IlmHamroh
JavaScript Full-stack/20-qism. React: UI kutubxonalar va animatsiya2/30-dars25 daqiqa
Mundarija (43)

shadcn/ui I: qanday ishlaydi va sozlash — Tailwind v4, cn, cva va CLI

Qisqacha: shadcn/ui — npm paket emas. npx shadcn add button buyrug'i registrdan komponent kodini olib, src/ papkangizga yozadi. Bu kod to'rt narsaga tayanadi: Tailwind sinflari, CSS o'zgaruvchilaridagi tokenlar, cn() yordamchisi va @/ alias. Ularni loyihada qayerda topishni CLI components.json faylidan o'qiydi. CLI kodni ko'chiradi, lekin versiyani tekshirmaydi — eng yangi paketni ^ bilan qo'shishi mumkin. Shuning uchun har add dan keyin git diff o'qiladi va versiyalar qotiriladi.

Bu darsda

  • shadcn/ui'ning to'rt qismini (registr, CLI, components.json, sizning fayllaringiz) va ular qanday bog'lanishini tushuntirasiz.
  • vazifalar ga Tailwind v4 ni preflight'siz ulaysiz va 18-qism tokenlarini @theme ga ko'chirasiz — ko'rinish bir pikselga ham o'zgarmaydi.
  • Tugma ni cva bilan qayta yozasiz, cn() nega faqat clsx ekanini raqam bilan asoslaysiz.
  • @/ alias va components.json ni sozlaysiz, shadcn CLI'ning haqiqiy chiqishidagi to'rt tuzoqni taniysiz.

Oldin bilishingiz kerak: UI kutubxonalar xaritasi, Tailwind CSS 3: @theme, Tailwind CSS 4: cn(), cva, Path alias, Dizayn tizimi: tokenlar.

1. Nega bu kerak?

O'tgan darsda copy-paste yondashuvini ko'rdik: kod src/ ga ko'chadi va sizniki bo'ladi. Keyingi darsda vazifalar ga shu yo'l bilan birinchi komponentni — Radix menyusini qo'shamiz. Lekin bugun buni qilsak, ko'chirilgan fayl ishlamaydi.

Sababi — shadcn komponenti "yolg'iz" emas. Uning bitta qatorini o'qiymiz:

tsx
<button className={cn(buttonVariants({ variant }), className)} />

Bu qatorda to'rt narsa bor va ularning hech biri vazifalar da hali yo'q:

  1. buttonVariants ichida Tailwind sinflari (bg-primary, h-9) — Tailwind ulanmagan bo'lsa, ular oddiy so'z bo'lib qoladi.
  2. bg-primary — token: rang qiymati CSS o'zgaruvchisida turadi.
  3. cn() — import { cn } from "@/lib/utils" dan keladi.
  4. @/ — papka taxallusi (alias): sozlanmasa, import topilmaydi.

Hayotiy o'xshatish: yangi oshxona jihozini olib keldingiz. Ishlashi uchun uyda to'g'ri rozetka, quvur va joy kerak. Avval uyni tayyorlaysiz, keyin jihozni ulaysiz. Bu dars — "uyni tayyorlash".

2. shadcn/ui qanday ishlaydi

2.1 To'rt qism

sequenceDiagram
  participant D as Siz
  participant C as shadcn CLI
  participant J as components.json
  participant R as Registr
  D->>C: npx shadcn add dropdown-menu
  C->>J: papkalar, alias, stil?
  C->>R: dropdown-menu.json
  R-->>C: fayllar matni + bog'liqliklar
  C->>D: src/ui/dropdown-menu.tsx
  C->>D: npm install (bog'liqliklar)

Nimaga qarang: CLI hech narsani "o'rnatmaydi" — u fayl yozadi va npm'ni chaqiradi. Qaysi papkaga yozishni, importlarni qanday yozishni components.json dan oladi.

  • Registr — komponentlar katalogi (o'tgan darsda tanishdik). Har komponent — bitta JSON.
  • CLI — npx shadcn@<versiya> (npx — paketni o'rnatmasdan bir marta ishga tushirish).
  • components.json — loyihangiz haqida CLI uchun "pasport".
  • Sizning fayllaringiz — CLI yozib bo'lgach, ular oddiy kod. CLI ularni boshqa kuzatmaydi.

2.2 Registr elementi ichida

Registrdagi bitta komponent — radix-nova uslubidagi dropdown-menu. Uni ui.shadcn.com dan o'qidik (2026-10-17; content — fayl matni, qisqartirilgan):

json
{
  "name": "dropdown-menu",
  "type": "registry:ui",
  "dependencies": ["cn"],
  "files": [
    {
      "path": "registry/radix-nova/ui/dropdown-menu.tsx",
      "type": "registry:ui",
      "content": "\"use client\" … import { cn } from \"cn\" …"
    }
  ]
}

Nimaga qarang: JSON — oddiy ma'lumot, fayl matni uning ichida satr bo'lib turibdi.

  • dependencies — npm paketlar, CLI ularni o'rnatadi. Bu yerda faqat cn — nega, «shadcn CLI» bo'limida ko'ramiz.
  • files — fayl matnlari, CLI ularni yozadi.

content boshidagi import { cn } from "cn" ni eslab qoling — u keyinroq tuzoq bo'lib chiqadi. Fayl ichidagi importlar (@/…) yozishdan oldin components.json dagi alias'larga moslanadi — hech bo'lmasa shunday bo'lishi kerak. Kanonda bu va'da to'liq bajarilmadi — «shadcn CLI» bo'limida ko'ramiz.

2.3 Asos: Radix yoki Base UI

shadcn/ui ikki headless kutubxona ustida ishlaydi. init da tanlanadi: -b radix yoki -b base. shadcn 4 ning standarti — Base UI. O'tgan darsdagi o'lchovni eslang: Base UI varianti — 93.2 KB va 0 CSP buzilishi, Radix + sonner — 90.8 KB va 4 buzilish.

vazifalar kanoni baribir Radix da qoldi. Sababi: keyingi ikki dars Radix'ning o'zi haqida (shadcn/ui II, Radix chuqur). Menyuni modal={false} bilan ishlatsak, Radix ham CSP'ni buzmaydi — o'lchovi Radix chuqur darsida. Buni halol aytamiz: CSP nuqtai nazaridan Base UI yaxshiroq natija ko'rsatdi. Yangi loyihada uni birinchi navbatda sinab ko'ring.

3. Tailwind v4 vazifalar da

3.1 O'rnatish

Tailwind'ni CSS qismida o'rgangansiz. React + Vite loyihasida ulash — o'sha ikki paket:

bash
npm i -D --save-exact tailwindcss@4.3.3 @tailwindcss/vite@4.3.3

-D — faqat build uchun: brauzerga paketning o'zi emas, u yasagan CSS boradi. vite.config.ts da plagin (kanondan):

ts
import tailwindcss from "@tailwindcss/vite";
// ...
plugins: [
  react(),
  // 20/#02: Tailwind CSS v4 — vazifalar.css dagi @import/@theme ni
  // qayta ishlaydi, src/ dagi sinflardan faqat ishlatilganlarini
  // yozadi
  tailwindcss(),
  // ...
],

3.2 Preflight'siz kirish

Odatdagi kirish — bitta qator: @import "tailwindcss";. U uch qismni ulaydi: tokenlar (theme), preflight (Tailwind'ning reset'i) va utilitalar. vazifalar da preflight'ni ATAYLAB ulamadik. U h1 ni oddiy matn o'lchamiga tushiradi, ro'yxat belgilarini va chegaralarni olib tashlaydi. Bu 18-qismdan beri sozlangan ko'rinishni buzardi.

Kanondagi src/vazifalar.css boshi (izohlar qisqartirilgan):

css
/* Qatlamlar tartibi: keyingisi oldingisidan ustun */
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
/* source("./") — sinflar faqat src/ dan qidiriladi
   (testlar, skriptlar va README dagi so'zlar CSS'ga tushmasin) */
@import "tailwindcss/utilities.css" layer(utilities) source("./");
@import "./asos.css" layer(base);

Uch qaror:

  • Preflight yo'q — theme.css va utilities.css alohida ulanadi (Tailwind hujjatidagi usul). Brauzer tekshiruvi: h1 — brauzer standarti, 32 px.
  • source("./") — Tailwind sinf nomlarini faqat src/ dan qidiradi. Aks holda README'dagi "flex" so'zi ham CSS'ga sinf bo'lib tushardi.
  • Eski CSS — asos.css ga, base qatlamiga. 13–19-qismlardagi hamma qoida shu faylga ko'chdi.

3.3 Nega eski CSS qatlamda bo'lishi shart

Tailwind CSS 3 darsidan eslang: qatlamsiz CSS har qanday qatlamdan ustun. Agar asos.css dagi button { padding: … } qatlamsiz qolsa, tugmaga yozilgan har px-4 utilitasi yutqazadi. Specificity'si kichik bo'lsa ham shunday, chunki qatlam tartibi specificity'dan oldin tekshiriladi.

@import "./asos.css" layer(base) bilan eski qoidalar eng past qatlamga tushdi. Endi utilita yozilgan joyda utilita yutadi, yozilmagan joyda eski qoida ishlaydi. Natija — kanon o'lchovi (natijalar/02-korinish.txt, 412 va 1280 px):

text
00-v9 → 02: 204/204 element, 3876 xususiyat, farq 0

204 element, har birining 19 ta hisoblangan xususiyati (rang, o'lcham, chegara…) Tailwind'dan oldin va keyin solishtirildi — farq 0. Ilova ko'rinishi bir pikselga ham o'zgarmadi.

Tekshirib ko'ring: @import "./asos.css" layer(base); o'rniga @import "./asos.css"; (qatlamsiz) yozilsa, <Tugma className="px-6"> ning chap-o'ng bo'shlig'i qancha bo'ladi?

Javob

asos.css dagi button qoidasidagi padding: --spacing(2) --spacing(3) — ya'ni 0.75rem (12 px). Qatlamsiz CSS utilities qatlamidan ustun, shuning uchun px-6 (1.5rem) ishlamaydi. Bunday xato juda chalg'itadi: DevTools'da px-6 ko'rinadi, lekin ustidan chizilgan bo'ladi. Davosi — eski CSS'ni qatlamga qo'yish.

4. Tokenlar @theme ga

4.1 18-qism tokenlari Tailwind nomlar fazosida

Dizayn tizimi darsida tokenlarni :root da e'lon qilgandik. Endi ular @theme ga ko'chdi. Nom — Tailwind nomlar fazosi (namespace) + bizning nom:

Eski (18/#33) Yangi (@theme) Utilita
--rang-asosiy --color-asosiy bg-asosiy, text-asosiy
--burchak --radius-burchak rounded-burchak
--bosish (44 px) --spacing-bosish min-h-bosish
--joy-2 Tailwind'ning --spacing p-2 (0.5rem)

Kanondan (qisqartirilgan):

css
@theme {
  /* standart palitra o'chiriladi: faqat ilova ranglari */
  --color-*: initial;
  --color-matn: #1f2a24;
  --color-yuza: #fff;
  --color-asosiy: #1f6f4a;
  --color-xato: #b3261e;
  --radius-burchak: 0.5rem;
  /* Bosiladigan element balandligi: 44 px — WCAG 2.5.5 */
  --spacing-bosish: 2.75rem;
}

--color-*: initial — Tailwind'ning tayyor palitrasi (red-500, slate-700 …) o'chadi. Endi tasodifiy bg-blue-600 ishlamaydi: CSS'ga hech narsa yozilmaydi. Faqat ilova ranglari qoladi — dizayn tizimini buzish qiyinlashadi.

4.2 Qorong'i rejim: light-dark() qatlamda

18-qismda qorong'i rejim light-dark() va color-scheme bilan qilingan edi. Uni @supports bilan o'ralgan: eski brauzer yorug' zaxirani ko'radi. Lekin @theme ichida @supports yozib bo'lmaydi. Shuning uchun haqiqiy qiymatlar base qatlamida almashtiriladi:

css
@layer base {
  @supports (color: light-dark(white, black)) {
    :root {
      color-scheme: light dark;
      --color-asosiy: light-dark(#1f6f4a, #7fd1a5);
      /* ... qolgan ranglar */
    }
  }
}

Bu yerda dark: varianti umuman kerak emas. bg-asosiy doim var(--color-asosiy), o'zgaruvchining qiymati esa rejimga qarab almashadi. Bu — CSS o'zgaruvchilari bilan tema: sinflar o'zgarmaydi, faqat qiymatlar.

4.3 shadcn'ning tema usuli

shadcn ham aynan shu g'oyani ishlatadi, faqat nomlari boshqa. init yozadigan CSS — ui.shadcn.com «Theming» sahifasidagi Tailwind v4 namunasidan (2026-10-17, qisqartirilgan):

css
:root {
  --background: oklch(1 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
}
.dark {
  --background: oklch(0.145 0 0);
  /* ... */
}
@theme inline {
  --color-background: var(--background);
  --color-primary: var(--primary);
}

Qiymatlar :root va .dark da, @theme inline (C-98 dagi "ko'prik") ularni utilitalarga ulaydi: bg-primary. Qorong'i rejim — .dark klassi bilan. components.json dagi "cssVariables": true — "ranglar CSS o'zgaruvchilarida" degani.

Bizning tokenlar shadcn nomlariga mos emas: bg-primary emas, bg-asosiy. Ikki yo'l bor: shadcn nomlariga o'tish yoki ko'chirilgan komponentdagi sinflarni o'zimiznikiga almashtirish. Kanon ikkinchisini tanladi — 18-qismdan beri butun ilova asosiy, yuza, xato bilan yashaydi. Almashtirishning o'zi keyingi darsda bo'ladi (bg-popover → bg-yuza).

5. cva va cn(): Tugma

5.1 Variantlar jadvali

18-qismda Tugma variantni data-variant atributiga aylantirardi, ranglar esa CSS'da edi. Endi variant to'g'ridan-to'g'ri sinflarga aylanadi — C-99 dagi cva, shadcn'ning Button i ham shu naqshda. Natija oynasida cva va clsx ishlaydi, Tailwind esa yo'q. Shuning uchun quyida tugmalar bezaksiz chiqadi: biz faqat hosil bo'lgan sinf nomlarini konsolda ko'ramiz.

tsx
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { clsx } from "clsx";
import type { ClassValue } from "clsx";
import type { ComponentPropsWithoutRef } from "react";

function cn(...classes: ClassValue[]): string {
  return clsx(classes);
}

const tugmaSinflari = cva(
  "tugma min-h-bosish rounded-burchak border px-3 py-2",
  {
    variants: {
      variant: {
        oddiy: "border-chegara bg-yuza text-matn",
        asosiy: "border-asosiy bg-asosiy text-asosiy-ustida",
        xavfli: "border-xato bg-yuza text-xato",
      },
    },
    defaultVariants: { variant: "oddiy" },
  },
);

type Props = ComponentPropsWithoutRef<"button"> &
  VariantProps<typeof tugmaSinflari>;

function Tugma({
  variant,
  type = "button",
  className,
  ...rest
}: Props) {
  return (
    <button
      type={type}
      className={cn(tugmaSinflari({ variant }), className)}
      {...rest}
    />
  );
}

export default function App() {
  const logClasses = (el: HTMLDivElement | null) => {
    if (!el) return;
    for (const b of el.querySelectorAll("button")) {
      console.log(b.className);
    }
  };
  return (
    <div ref={logClasses}>
      <Tugma>Bekor qilish</Tugma>
      <Tugma variant="xavfli" className="ml-2">
        O'chirish
      </Tugma>
    </div>
  );
}

Konsolda:

text
tugma min-h-bosish rounded-burchak border px-3 py-2 border-chegara bg-yuza text-matn
tugma min-h-bosish rounded-burchak border px-3 py-2 border-xato bg-yuza text-xato ml-2

Birinchi tugmada variant berilmadi — defaultVariants dagi oddiy sinflari qo'shildi. Ikkinchisida — xavfli va chaqiruvchi bergan ml-2 (joylashuv). tugma sinfi qoldi: asos.css dagi eski qoidalar va testlar unga tayanadi.

5.2 Tur jadvaldan keladi

VariantProps<typeof tugmaSinflari> — variant ning turi jadvalning o'zidan olinadi. Jadvalga yangi variant qo'shsangiz, tur o'zi kengayadi. Mavjud bo'lmagan variant esa tsc xatosi:

tsx
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";

const tugmaSinflari = cva("tugma", {
  variants: { variant: { oddiy: "a", xavfli: "b" } },
});
type Props = VariantProps<typeof tugmaSinflari>;
const props: Props = { variant: "qizil" };

Xato:

text
TS2322: Type '"qizil"' is not assignable to type '"oddiy" | "xavfli" | null | undefined'.

Tarjimasi: ""qizil" turi "oddiy" | "xavfli" | null | undefined turiga mos kelmaydi." null ham ruxsat: cva da variant: null — "variant sinfini qo'shma" degani. Tugmaning API'si (Komponent API'sini loyihalash) o'zgarmadi: chaqiruvchilarning birortasiga tegilmadi.

5.3 cn() = faqat clsx

C-99 darsida cn() ni clsx + tailwind-merge deb o'rgangan edik. tailwind-merge to'qnashuvchi sinflardan oxirgisini qoldiradi: cn("px-3", "px-4") → "px-4". Kanondagi cn esa faqat clsx:

jsx
import { clsx } from "clsx";

console.log(clsx("px-3", "px-4"));
console.log(clsx("tugma", false && "faol", { xato: true }));

Konsolda:

text
px-3 px-4
tugma xato

Ikkala px ham qoldi — qaysi biri yutishini CSS fayldagi tartib hal qiladi. Nega merge yo'q? O'lchadik (natijalar/02-cn-hajm.txt, birinchi yuklash JS, gzip):

vazifalar: birinchi yuklash JS (gzip)
  • v9 (Tailwind'siz)151,4 KB
  • clsx + cva (kanon)151,9 KB
  • + tailwind-merge 3.7.0160,6 KB
  • shadcn'ning cn 0.4.0 + cva162,8 KB

Manba: O'lchandi: holat 02, Vite 8.3.3, gzip -9, natijalar/02-cn-hajm.txt, 2026-10-14

clsx va cva — atigi +0.5 KB. tailwind-merge yana +8.8 KB qo'shardi: u Tailwind'ning butun sinf tizimini biladigan kichik "tahlilchi". shadcn 4 CLI esa endi cn ni alohida cn paketidan (0.4.0) oladi — cva bilan birga +11.4 KB.

Merge'siz yashash uchun kanon qoidasi (TEXNIK-QARZ.md, 53-band): komponentga className faqat qo'shimcha uchun — joylashuv (ml-2, flex-1). Rang va o'lcham — faqat variant orqali. Shunda to'qnashuvchi sinf umuman berilmaydi. Komponentlar kutubxonasi kattalashsa yoki tashqaridan className bilan bezash kerak bo'lsa, tailwind-merge qo'shiladi.

Tekshirib ko'ring: Kanon qoidasiga ko'ra <Tugma className="bg-xato"> yozish to'g'rimi?

Javob

Yo'q. bg-xato — rang, tugmaSinflari dagi bg-yuza bilan to'qnashadi. Merge yo'q, ikkalasi ham qoladi, yutadigan sinf CSS'dagi tartibga bog'liq — natija tasodifiy. To'g'ri yo'l — variant: variant="xavfli" yoki jadvalga yangi variant qo'shish. className — faqat joylashuv uchun (ml-2).

5.4 asChild: shadcn Button ichida

Polymorphic komponentlar darsida va'da bergan edik: shadcn Button asChild ni Radix Slot bilan qiladi. Natija oynasida @radix-ui/react-slot bor — haqiqiy kutubxona bilan ko'ramiz:

jsx
import { Slot } from "@radix-ui/react-slot";
import { clsx } from "clsx";

function Button({ asChild = false, className, ...props }) {
  // shadcn naqshi: asChild bo'lsa — o'z tegi yo'q,
  // sinflar bolasiga "kiyiladi"
  const Comp = asChild ? Slot : "button";
  return <Comp className={clsx("tugma", className)} {...props} />;
}

export default function App() {
  return (
    <nav ref={(el) => el && console.log(el.innerHTML)}>
      <Button>Saqlash</Button>
      <Button asChild className="havola">
        <a href="/menyu">Menyu</a>
      </Button>
    </nav>
  );
}

Konsolda:

text
<button class="tugma">Saqlash</button><a href="/menyu" class="tugma havola">Menyu</a>

Ikkinchi "tugma" — aslida <a>. Slot o'z tegini chizmadi, sinflarni bolasiga birlashtirdi: havola tugma ko'rinishida, lekin havola bo'lib qoladi. Shunday qilib Link (React Router) ham <Button asChild> ichida ishlaydi. Tugma kanonda asChild siz — vazifalar da bunga ehtiyoj hali yo'q.

6. @/ alias va components.json

6.1 Alias — to'rt joyda

Path alias darsida @/ ni sinab ko'rib, vazifalar ga qo'shmagan edik: nisbiy yo'llar yetardi. Endi sabab bor — shadcn CLI fayllarni @/ importlari bilan yozadi. O'sha darsdagi qoida: har vosita alias'ni alohida biladi.

Fayl Kim uchun Nima yoziladi
tsconfig.app.json tsc, tahrirlovchi "paths": { "@/*": ["./src/*"] }
tsconfig.json (ildiz) shadcn CLI xuddi shu paths
vite.config.ts dev va build resolve.alias
vitest.config.ts komponent testlari resolve.alias

Ildiz tsconfig.json — yangilik. U odatda faqat references saqlaydi, lekin shadcn CLI alias'ni aynan shu fayldan qidiradi. Kanonda alias'siz birinchi urinishda CLI "Validating import alias" qadamida to'xtagan. Vite va Vitest tsconfig'ni o'qimaydi — shuning uchun ularda alohida:

ts
resolve: {
  alias: {
    // import "@/ui/cn.ts" → src/ui/cn.ts (tsconfig.app.json
    // "paths" bilan bir xil; Vite tsconfig'ni o'zi o'qimaydi)
    "@": resolve(import.meta.dirname, "src"),
  },
},

Bitta istisno: node --test tekshiradigan sof .ts fayllar nisbiy yo'lda qoladi. Node alias'ni bilmaydi (Path alias darsidagi #/ — Node'ning o'z yo'li). Qoida: @/ — faqat Vite ko'radigan fayllarda.

6.2 components.json

Kanondagi fayl, to'liq:

json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "radix-nova",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/vazifalar.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@/komponentlar",
    "utils": "@/ui/cn",
    "ui": "@/ui",
    "lib": "@/ui",
    "hooks": "@/hooklar"
  }
}
Maydon Ma'nosi
style komponentlar uslubi va asosi: radix-nova — Radix ustidagi shadcn 4 uslubi
rsc React Server Components: false — bizda server komponent yo'q (Next.js qismida)
tailwind.css tokenlar va @import qaysi faylda
tailwind.config bo'sh — v4 da JS konfiguratsiya yo'q
aliases.ui add fayllarni qayerga yozadi: @/ui → src/ui/
aliases.utils cn qayerdan import qilinadi: @/ui/cn

iconLibrary: "lucide" — komponentlar ichidagi ikonkalar lucide-react dan (Ikonkalar darsida). Diqqat qiling: utils alias'i yozilgan. Keyingi bo'limda CLI uni hisobga olmaganini ko'ramiz.

7. shadcn CLI: haqiqiy oqim va tuzoqlar

Kanonda holat 02 ning nusxasida haqiqiy CLI ishga tushirildi: shadcn@4.21.4, Node 24.21.0, npm 11.19.0 (natijalar/02-shadcn-cli.txt). Login so'ralmadi.

7.1 init Tailwind'ni topmadi

bash
npx shadcn@4.21.4 init -b radix -p nova -y --no-monorepo

Chiqish (fayldan):

text
✔ Preflight checks.
✔ Verifying framework. Found Vite.
✖ Validating Tailwind CSS. Found v4.
✔ Validating import alias.
No Tailwind CSS configuration found ... Install Tailwind CSS then try again.

Tarjimasi: "Tailwind CSS konfiguratsiyasi topilmadi… Tailwind CSS'ni o'rnatib, qayta urinib ko'ring." Tailwind o'rnatilgan va ishlayapti-ku! Sabab: init CSS faylda aynan @import "tailwindcss" qatorini qidiradi. Bizda preflight'siz kirish — theme.css va utilities.css alohida. CLI uni tanimaydi. Yechim — init siz: components.json ni qo'lda yozish (yuqoridagi fayl). add uchun shu yetadi.

Tuzoq №1: CLI "Tailwind yo'q" desa, Tailwind'ni qayta o'rnatishga shoshilmang. CLI faqat bitta odatiy shaklni taniydi.

7.2 add: fayllar bor, lekin…

bash
npx shadcn@4.21.4 add dropdown-menu tooltip -y
text
✔ Created 2 files: src\ui\dropdown-menu.tsx, src\ui\tooltip.tsx

Fayllar components.json dagi "ui": "@/ui" bo'yicha to'g'ri joyga yozildi. Endi git diff va tsc -b:

text
src/ui/dropdown-menu.tsx(3,55): error TS2307: Cannot find module 'radix-ui'
src/ui/dropdown-menu.tsx(4,45): error TS2307: Cannot find module 'lucide-react'
src/ui/tooltip.tsx(5,45): error TS2307: Cannot find module 'radix-ui'

Tarjimasi: "radix-ui modulini topib bo'lmadi" (tsc'ning to'liq xabari davomida "or its corresponding type declarations" — "yoki uning tur e'lonlarini"). To'rtta tuzoq birdan ko'rindi:

  • Tuzoq №2 — cn boshqa joydan. Fayllar import { cn } from "cn" deb yozilgan — registr JSON'idagi o'sha qator («Registr elementi ichida» bo'limi): npm'dagi cn paketidan. components.json dagi "utils": "@/ui/cn" ishlatilmadi. package.json ga esa "cn": "0.4.0" qo'shildi — bizning @/ui/cn.ts o'rniga (cva bilan birga +11.4 KB, clsx bilan esa +0.5).
  • Tuzoq №3 — bog'liqliklar o'rnatilmadi. radix-nova uslubida registr elementi faqat cn ni so'raydi: CLI radix-ui va lucide-react ni init o'rnatgan deb hisoblaydi. Biz init qilmadik — demak ular yo'q, uchta TS2307.
  • Tuzoq №4 — eng yangi versiya. new-york uslubida CLI radix-ui ni qo'shdi — 1.8.1, ikki kun oldin chiqqan versiya. Kursning qoidasi esa — 7 kundan yangi reliz olinmaydi.
  • Tuzoq №5 — ^. Loyihada .npmrc dagi save-exact bo'lmasa, "radix-ui": "^1.8.1" yoziladi (Semver): keyingi npm install yana yangisini olishi mumkin. save-exact bo'lsa ham CLI versiyani tanlamaydi, faqat ^ ni olib tashlaydi.

7.3 To'g'ri oqim

Kanon xulosasi — CLI'ga ishoning, lekin tekshiring:

bash
git status        # toza bo'lsin: CLI nima qilganini ko'rasiz
npx shadcn@4.21.4 add dropdown-menu
git diff                    # yangi fayllar va package.json
npm i -E @radix-ui/react-dropdown-menu@2.1.25   # qo'lda, aniq
npm run check               # tsc, lint, testlar
  1. CLI versiyasi aniq (shadcn@4.21.4, @latest emas) — bugungi buyruq ertaga ham o'sha natijani bersin.
  2. git diff — CLI qo'shgan importlar va paketlarni o'qing: "cn", eng yangi radix-ui, ^.
  3. Paketlarni o'zingiz qotiring: -E (--save-exact), 7 kundan eski versiya.
  4. Importlarni moslang: cn → @/ui/cn.ts, umumiy radix-ui → alohida @radix-ui/react-dropdown-menu (sababi keyingi darsda), sinflar → tokenlarimiz.

Kanondagi src/ui/ fayllari aynan shunday paydo bo'ldi: CLI chiqishi qisqartirildi va moslandi. Qismlar, data-slot atributlari va eksport nomlari CLI'nikiga mos. Batafsil — keyingi darsda.

7.4 Yangilash muammosi

Ertaga shadcn registrida dropdown-menu ning tuzatilgan versiyasi chiqsa, u sizga o'zi kelmaydi. Qayta add qilsangiz, CLI faylni registrdagi variant bilan almashtiradi — siz qilgan moslashlar o'sha faylda bo'lsa, ular yo'qoladi. Shuning uchun oqim yana o'sha: toza git status → add → git diff → kerakli qatorlarni qo'lda saqlash. Xulq qatlami (Radix) esa npm paketi — uning tuzatishlari odatdagi npm yangilanishi bilan keladi.

Diqqat: Eski maqolalarda npx shadcn-ui@latest init ni ko'rasiz — paketning eski nomi. Hozirgisi — shadcn. Tailwind v3 loyihalarida esa tailwind.config.js va tailwindcss-animate uchraydi; v4 da ularning o'rnida CSS'dagi @theme va tw-animate-css (Tailwind CSS 5).

8. Vazifalar qadami: Tailwind v4 va Tugma (holat 02)

8.1 Branch va paketlar

  • Branch: feat/tailwind
bash
git switch -c feat/tailwind
npm i -D -E tailwindcss@4.3.3 @tailwindcss/vite@4.3.3
npm i -E clsx@2.1.1 class-variance-authority@0.7.1

clsx va cva — dependencies da: ular brauzerga boradi. Tailwind — devDependencies da: brauzerga faqat u yasagan CSS boradi.

8.2 Fayllar

Fayl O'zgarish
src/vazifalar.css Tailwind kirishi: qatlamlar, @import, @theme
src/asos.css yangi: eski qoidalar, var(--rang-*) → var(--color-*)
src/ui/cn.ts, src/ui/Tugma.tsx cn va cva
vite.config.ts, vitest.config.ts Tailwind plagini, @/ alias
tsconfig.json, tsconfig.app.json paths
components.json, knip.json yangi

index.html dagi <link rel="stylesheet" href="src/vazifalar.css"> o'zgarmadi: Vite shu havolani ko'rib, CSS'ni Tailwind plaginidan o'tkazadi.

knip.json nima uchun? knip ishlatilmagan paket va fayllarni topadi. U asos.css ni ham, tailwindcss paketini ham "hech kim import qilmaydi" deb xato topardi. Sababi, ular faqat CSS @import orqali ulangan. "entry": ["src/vazifalar.css"] — knip'ga CSS kirish nuqtasini ko'rsatadi.

8.3 src/ui/cn.ts

Kanondan (izohlar qisqartirilgan):

ts
// cn.ts — sinflarni birlashtirish (20/#02): clsx — shartli
// sinflar ("a", ochiq && "b", { c: bool }) bitta satrga.
// tailwind-merge YO'Q — o'lchandi (natijalar/02-cn-hajm.txt):
// birinchi yuklash gzip +8.8 KB. Qoida: komponentga className faqat
// QO'SHIMCHA uchun (joylashuv), rang/o'lcham — variant orqali
// (TEXNIK-QARZ 53)
import { clsx } from "clsx";
import type { ClassValue } from "clsx";

export function cn(...sinflar: ClassValue[]): string {
  return clsx(sinflar);
}

8.4 src/ui/Tugma.tsx

Jadval qismi va komponent (kanondan, aynan):

tsx
import { cn } from "@/ui/cn.ts";

const tugmaSinflari = cva(
  "tugma min-h-bosish rounded-burchak border px-3 py-2 disabled:opacity-60",
  {
    variants: {
      variant: {
        oddiy: "border-chegara bg-yuza text-matn",
        asosiy: "border-asosiy bg-asosiy text-asosiy-ustida",
        xavfli: "border-xato bg-yuza text-xato",
      },
    },
    defaultVariants: { variant: "oddiy" },
  },
);

export function Tugma({
  variant,
  type = "button",
  className,
  ref,
  ...qolgan
}: Props) {
  return (
    <button
      ref={ref}
      type={type}
      className={cn(tugmaSinflari({ variant }), className)}
      {...qolgan}
    />
  );
}

@/ui/cn.ts — birinchi @/ import. disabled:opacity-60 — holat varianti (Tailwind CSS 2). asos.css dagi .tugma[data-variant="…"] qoidalari o'chirildi — ularning ishini endi cva qiladi.

8.5 Natija

Kanon tekshiruvi (natijalar/tekshiruv-02.txt): build, npm test (node:test 198, Vitest 67), lint, format, tip, knip — hammasi toza. Hajm (gzip): birinchi yuklash JS 151.4 → 151.9 KB, CSS 2.5 → 3.1 KB. CSS o'sdi — endi unda tokenlar va ishlatilgan utilitalar bor.

Ko'rinish farqi — 0, axe — 0 buzilish. Brauzer tekshiruvlari: 13–17-qismning 159 tasi, 18-qismning 72 tasi, 19-qismning 100 tasi o'tdi. 20-qismning yangi tekshiruvi e2e-20.mjs (brauzer-tekshiruv/natija-20-02.txt):

text
✅ qatlamlar tartibi: theme, base, components, utilities: ["theme","base","components","utilities"]
✅ Tugma (cva): .tugma, 44 px, fon — --color-asosiy: [true,"44px",true]
✅ preflight yo'q: h1 — brauzer standarti (2em = 32px): "32px"
✅ Tailwind: CSP buzilishi yo'q: []
✅ konsolda xato yo'q: []

To'rtinchi qatorga qarang: Tailwind CSP'ni buzmaydi. U CSS'ni build paytida faylga yozadi, sahifaga <style> qo'shmaydi. CSS-in-JS kitlardan farqi shu (o'tgan dars).

8.6 Commit

bash
git add -A
git commit -m "feat: Tailwind v4 (preflight'siz), tokenlar @theme'da, Tugma — cva"

Kanonda @/ alias va components.json keyinroq, alohida commit bilan qo'shilgan: build: @/ alias (tsconfig paths, vite/vitest) va components.json — shadcn CLI uchun. Siz ikkalasini bitta branch'da ketma-ket ikki commit qilishingiz mumkin. Holat 02 jami 13 fayl, +1208 −465 qator. Qatorlarning asosiy qismi — vazifalar.css ning asos.css ga ko'chishi va package-lock.json.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Tailwind sinfi yozilgan, lekin ta'sir yo'q eski CSS qatlamsiz — utilitalardan ustun eski CSS'ni layer(base) bilan
Sinf CSS'ga umuman tushmadi fayl source() dan tashqarida yoki nom yig'ilgan (bg-${rang}) source, to'liq sinf nomi (C-99)
bg-blue-600 ishlamaydi --color-*: initial tokenlardan foydalaning — bu ataylab
Vitest: Failed to resolve import "@/ui/cn.ts" alias faqat vite.config.ts da vitest.config.ts ga ham
TS2307: Cannot find module 'radix-ui' CLI bog'liqlikni o'rnatmadi npm i -E <paket>@<versiya>
package.json da "radix-ui": "^1.8.1" CLI eng yangisini ^ bilan qo'shdi aniq, 7 kundan eski versiya
init: "No Tailwind CSS configuration found" preflight'siz kirish components.json qo'lda

10. Mashqlar

Mashqlar kurs/mashqlar/20/02-shadcn-sozlash/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): bo'sh joylarni to'ldiring

  • Tailwind'ning reset qismi:
  • Qatlamsiz CSS qatlamdagi CSS'dan:
  • --color-asosiy tokenidan hosil bo'ladigan fon utilitasi: bg-
  • shadcn CLI papka va alias'larni o'qiydigan fayl:
  • Kanondagi cn() ichida faqat:
Yechim

Preflight — Tailwind'ning reset'i, vazifalar da ulanmagan. Qatlamsiz CSS har qatlamdan ustun — shuning uchun eski qoidalar layer(base) ga ko'chdi. @theme dagi --color-asosiy → bg-asosiy, text-asosiy, border-asosiy. components.json — CLI uchun loyiha pasporti. cn = clsx: tailwind-merge +8.8 KB bo'lgani uchun kirmadi.

2-mashq (o'rta): «Bahor» nishoni

«Bahor» buyurtmalar ro'yxatida holat nishoni (badge) kerak. cva bilan nishonSinflari yozing:

  • asos: nishon rounded-burchak px-2;
  • holat varianti: yangi → bg-belgi text-matn, tayyor → bg-asosiy text-asosiy-ustida, bekor → bg-yuza text-xato;
  • olcham varianti: kichik → text-sm, katta → text-lg;
  • sukut bo'yicha: holat: "yangi", olcham: "kichik";
  • compoundVariants: bekor va katta birga bo'lsa — qo'shimcha font-bold (C-99 dagi kabi).

Konsolga uch chaqiruvni chiqaring: sukut bilan, { holat: "tayyor" }, { holat: "bekor", olcham: "katta" }.

Yechim
jsx
import { cva } from "class-variance-authority";

const nishonSinflari = cva("nishon rounded-burchak px-2", {
  variants: {
    holat: {
      yangi: "bg-belgi text-matn",
      tayyor: "bg-asosiy text-asosiy-ustida",
      bekor: "bg-yuza text-xato",
    },
    olcham: { kichik: "text-sm", katta: "text-lg" },
  },
  compoundVariants: [
    { holat: "bekor", olcham: "katta", class: "font-bold" },
  ],
  defaultVariants: { holat: "yangi", olcham: "kichik" },
});

console.log(nishonSinflari());
console.log(nishonSinflari({ holat: "tayyor" }));
console.log(nishonSinflari({ holat: "bekor", olcham: "katta" }));

Konsolda:

text
nishon rounded-burchak px-2 bg-belgi text-matn text-sm
nishon rounded-burchak px-2 bg-asosiy text-asosiy-ustida text-sm
nishon rounded-burchak px-2 bg-yuza text-xato text-lg font-bold

Tartib: asos → variantlar (jadvaldagi tartibda) → compoundVariants. Ikkinchi chaqiruvda olcham berilmadi — sukutdagi kichik olindi. font-bold faqat uchinchisida: ikki shart birga bajarildi.

3-mashq (qiyin): Tugma ga asChild

«cva va cn(): Tugma» bo'limidagi Tugma ni «asChild: shadcn Button ichida» bo'limidagi g'oya bilan birlashtiring:

  • yangi prop asChild (sukut — false);
  • asChild bo'lsa — Slot (@radix-ui/react-slot), aks holda "button";
  • type="button" faqat haqiqiy <button> ga berilsin: <a type="button"> noto'g'ri HTML;
  • sinflar — o'sha tugmaSinflari jadvalidan.

Sinov: <Tugma>Saqlash</Tugma> va <Tugma asChild variant="asosiy"><a href="/menyu">Menyu</a></Tugma> ni chizing va nav ning innerHTML ini konsolga chiqaring.

Yechim
jsx
import { Slot } from "@radix-ui/react-slot";
import { cva } from "class-variance-authority";
import { clsx } from "clsx";

const tugmaSinflari = cva("tugma rounded-burchak px-3", {
  variants: {
    variant: {
      oddiy: "bg-yuza text-matn",
      asosiy: "bg-asosiy text-asosiy-ustida",
    },
  },
  defaultVariants: { variant: "oddiy" },
});

function Tugma({ asChild = false, variant, className, ...rest }) {
  const Comp = asChild ? Slot : "button";
  // type faqat <button> uchun; Slot'da bolaning o'z atributlari
  const typeProp = asChild ? {} : { type: "button" };
  return (
    <Comp
      {...typeProp}
      className={clsx(tugmaSinflari({ variant }), className)}
      {...rest}
    />
  );
}

export default function App() {
  return (
    <nav ref={(el) => el && console.log(el.innerHTML)}>
      <Tugma>Saqlash</Tugma>
      <Tugma asChild variant="asosiy">
        <a href="/menyu">Menyu</a>
      </Tugma>
    </nav>
  );
}

Konsolda:

text
<button type="button" class="tugma rounded-burchak px-3 bg-yuza text-matn">Saqlash</button><a href="/menyu" class="tugma rounded-burchak px-3 bg-asosiy text-asosiy-ustida">Menyu</a>

Havola asosiy variantdagi tugma sinflarini oldi, lekin <a> bo'lib qoldi. Malika uni "havola" deb eshitadi, Ctrl + bosish esa yangi tabda ochadi. type esa faqat haqiqiy tugmada. shadcn Button ham aynan shunday tuzilgan — farqi, unda cn va o'lcham (size) varianti bor.

4-mashq: vazifalar qadami

feat/tailwind branch'ida «Vazifalar qadami» ni bajaring: paketlar, vazifalar.css + asos.css, @theme tokenlari, ui/cn.ts, Tugma — cva, @/ alias (to'rt joy), components.json, knip.json. npm run check toza bo'lsin. Keyin:

  1. DevTools → Elements'da <h1> ning shrift o'lchamini (Computed) Tailwind'dan oldingi bilan solishtiring.
  2. Biror tugmaga vaqtincha className="bg-blue-600" yozing va natijaga qarang.
  3. vitest.config.ts dan alias'ni vaqtincha olib tashlab, npm test ni ishga tushiring.
Yechim
  1. 32 px — o'zgarmagan: preflight ulanmagan, h1 brauzer standartida qoldi.
  2. Hech narsa o'zgarmaydi: --color-*: initial standart palitrani o'chirgan, bg-blue-600 uchun CSS yozilmaydi. Bu himoya — dizayn tizimidan tashqari rang ishlamaydi.
  3. Vitest Tugma.tsx dagi @/ui/cn.ts importini topa olmaydi va Tugma ni ishlatadigan testlar yiqiladi. Alias'ni qaytaring — vitest tsconfig'ni ham, vite.config.ts ni ham o'qimaydi.

11. Real ishda

  • Yangi loyiha: ko'p jamoalar npm create vite → Tailwind → npx shadcn init bilan boshlaydi. Odatiy loyihada init muammosiz o'tadi. Muammo — mavjud, o'z CSS'i bor loyihaga qo'shishda, xuddi biznikidek.
  • Kod ko'rib chiqish (code review)da: shadcn add dan keyingi PR'da avval package.json qarashadi — qanday paketlar, qanday versiyalar, ^ bormi.
  • Dizayn tizimi jamoalari tokenlarni @theme da ushlaydi va o'z registrini yuritadi: boshqa loyihalar npx shadcn add https://…/tugma.json bilan oladi.
  • Intervyu savollari: "shadcn/ui'ni qanday yangilaysiz?", "cn() ichida nima bor va nega?", "cva nima beradi?", "Tailwind v4 da preflight nima?", "@/ alias'ni qayerlarda sozlash kerak?".

Xulosa

  • shadcn/ui: registr → CLI → src/ dagi fayl; CLI components.json dan papka va alias'larni o'qiydi.
  • Tailwind v4 vazifalar da preflight'siz: theme.css + utilities.css source("./"), eski CSS — layer(base); ko'rinish farqi 0.
  • Tokenlar @theme da (--color-asosiy → bg-asosiy), --color-*: initial; qorong'i rejim — light-dark() bilan, dark: siz.
  • Tugma — cva jadvali, tur VariantProps dan; cn = clsx (+0.5 KB), tailwind-merge yo'q (+8.8 KB) — className faqat joylashuv uchun.
  • @/ — tsconfig (ikkala), Vite, Vitest; Node testlari nisbiy yo'lda.
  • CLI tuzoqlari: init Tailwind'ni tanimadi, cn paketdan, bog'liqliklar yo'q (TS2307), eng yangi versiya va ^ — git diff va npm i -E bilan.

Keyingi dars: shadcn/ui II: real ilovada asosiy komponentlar — dashboard'ning shadcn "g'ishtlari" va vazifalar ga ko'chirilgan DropdownMenu: bandlar turlari, modal={false} va <main> dagi portal (o'lchovi bilan sababi — Radix chuqur da), narxi +27.2 KB.

Manbalar

  • shadcn/ui: "Installation — Vite", "components.json", "CLI", "Registry", "Theming" — ui.shadcn.com/docs
  • Tailwind CSS v4: "Installing with Vite", "Preflight" (preflight'siz ulash), "Theme variables", "Detecting classes in source files" — tailwindcss.com/docs
  • class-variance-authority 0.7.1: "Variants", "Compound Variants", "TypeScript" — cva.style/docs
  • clsx 2.1.1 — github.com/lukeed/clsx; tailwind-merge — github.com/dcastil/tailwind-merge
  • Radix: "Slot" — radix-ui.com/primitives/docs/utilities/slot
  • shadcn registr elementi: ui.shadcn.com/r/styles/radix-nova/dropdown-menu.json (2026-10-17)
  • Kanon: vazifalar holat 02, natijalar/02-shadcn-cli.txt, 02-cn-hajm.txt, 02-korinish.txt, tekshiruv-02.txt (2026-10-14)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
shadcn/ui I: qanday ishlaydi va sozlash — Tailwind v4, cn, cva va CLI — IlmHamroh