Dark mode
Qorong'i rejimni data-theme bilan yoqish, next-themes va mavzu almashtirgich.
Qorong'i rejim bitta atribut bilan yoqiladi:
<html data-theme="dark"></html>Shu atribut bo'lsa, barcha semantik tokenlar (--ak-surface, --ak-text, --ak-primary-soft
...) dark qiymatlarga o'tadi va komponentlar qo'shimcha ishsiz qorong'i ko'rinishga keladi.
Atribut bo'lmasa yoki data-theme="light" bo'lsa — light rejim. Tailwind'ning dark: varianti
ham shu atributga bog'langan.
'use client';
import { Badge, Button, Field, Input } from '@akhatmirzo/ui';
// data-theme istalgan elementga qo'yiladi — tokenlar faqat shu blok ichida almashadi
export default function ThemingScoped() {
return (
<div className="grid w-full gap-4 sm:grid-cols-2">
{(['light', 'dark'] as const).map((theme) => (
<div
key={theme}
data-theme={theme}
className="flex flex-col gap-4 rounded-xl border border-border bg-background p-5 text-text"
>
<div className="flex items-center justify-between">
<span className="text-sm font-semibold">data-theme="{theme}"</span>
<Badge color="success" dot>
Faol
</Badge>
</div>
<Field label="Ism">
<Input placeholder="Sarvar" />
</Field>
<div className="flex gap-2">
<Button variant="primary">Saqlash</Button>
<Button variant="outline">Bekor qilish</Button>
</div>
</div>
))}
</div>
);
}
Tizim sozlamasi avtomatik kuzatilmaydi
theme.css prefers-color-scheme ga qaramaydi — qaysi rejim yoqilishini ilova hal qiladi.
Shunday qilib foydalanuvchi tanlovi, tizim sozlamasi va saqlangan qiymat o'rtasida ziddiyat
bo'lmaydi. Quyida ikkala usul ham ko'rsatilgan.
next-themes bilan (Next.js)
next-themes mavzuni saqlaydi, tizim sozlamasini kuzatadi va sahifa yuklanganda "oq chaqnash"ning oldini oladi. Unga atribut nomini ayting:
pnpm add next-themes'use client';
import { ConfirmProvider, Toaster, TooltipProvider } from '@akhatmirzo/ui';
import { ThemeProvider } from 'next-themes';
import type { ReactNode } from 'react';
export function Providers({ children }: { children: ReactNode }) {
return (
<ThemeProvider attribute="data-theme" defaultTheme="system" enableSystem>
<TooltipProvider>
<ConfirmProvider>
{children}
<Toaster />
</ConfirmProvider>
</TooltipProvider>
</ThemeProvider>
);
}next-themes <html> atributini sahifa chizilishidan oldin o'zgartiradi, shuning uchun
layout.tsx da suppressHydrationWarning qo'ying:
<html lang="uz" suppressHydrationWarning>Loyihada class="dark" ga tayanadigan boshqa kutubxona ham bo'lsa, ikkala atributni birga
bering: attribute={['class', 'data-theme']}. Shu hujjatlar sayti ham aynan shunday ishlaydi.
Mavzu almashtirgich
'use client';
import { IconButton } from '@akhatmirzo/ui';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
export function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme();
const dark = resolvedTheme === 'dark';
return (
<IconButton
variant="ghost"
aria-label={dark ? "Yorug' rejimga o'tish" : "Qorong'i rejimga o'tish"}
onClick={() => setTheme(dark ? 'light' : 'dark')}
>
{dark ? <Sun /> : <Moon />}
</IconButton>
);
}resolvedTheme server render'da undefined bo'ladi. Ikonka hydration'dan keyin almashsa,
bu normal holat.
Kutubxonasiz (Vite va boshqalar)
Mavzuni o'zingiz boshqarsangiz, ikki narsa kerak: sahifa chizilishidan oldin atributni qo'yadigan kichik skript va uni o'zgartiradigan hook.
<head>
<script>
try {
// useLocalStorage qiymatni JSON qilib yozadi: "dark"
const saved = JSON.parse(localStorage.getItem('theme') || 'null');
const system = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.documentElement.dataset.theme = saved || system;
} catch {}
</script>
</head>import { useLocalStorage } from '@akhatmirzo/hooks';
import { useEffect } from 'react';
type Theme = 'light' | 'dark';
export function useTheme() {
const [theme, setTheme] = useLocalStorage<Theme>('theme', () =>
document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light',
);
useEffect(() => {
document.documentElement.dataset.theme = theme;
}, [theme]);
return [theme, setTheme] as const;
}Skript nega kerak
Atribut faqat React yuklangandan keyin qo'yilsa, foydalanuvchi bir lahzaga oq sahifani ko'radi.
<head> dagi skript bu chaqnashni yo'qotadi.
Sahifaning bir qismida
data-theme faqat <html> ga emas, istalgan elementga qo'yilishi mumkin — tokenlar shu element
ichida almashadi. Yuqoridagi demo aynan shunday qurilgan: masalan, yorug' sahifada qorong'i
footer yoki mavzularni yonma-yon solishtirish uchun.
<footer data-theme="dark" className="bg-background text-text">
...
</footer>Overlay'lar portal orqali ochiladi
Dialog, Popover, DropdownMenu, Tooltip va toast'lar document.body ga render bo'ladi,
shuning uchun ular blokning emas, <html> ning mavzusini oladi.
Dark rejimda nima o'zgaradi
- Fonlar. Sahifa — slate-900, karta va input — slate-800, popover va toast — bir pog'ona ochroq (slate-750). Qorong'i rejimda "balandlik" soya bilan emas, ochroq fon bilan ko'rinadi.
- Soyalar.
shadow-mdva undan kattalariga 1px halqa qo'shiladi. - Primary tugma o'zgarmaydi (indigo-600) — kontrast uchun. Hover, soft fonlar va havolalar esa ochroq bo'ladi.
- Native elementlar.
color-scheme: darko'rnatiladi — scrollbar, sana tanlagich va autofill ham qorong'i rejimga o'tadi.
O'z stillaringizda
Semantik tokenlar bilan yozsangiz, dark: deyarli kerak bo'lmaydi: bg-surface text-text border-border ikkala rejimda ham to'g'ri. dark: ni faqat haqiqatan boshqa qaror kerak bo'lgan
joyda ishlating — masalan, rasmni qorong'i fonda xiraroq qilish uchun: dark:opacity-80.
Tizim sozlamasini o'zingiz tekshirmoqchi bo'lsangiz —
useMediaQuery:
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');