Compare commits

..

45 Commits

Author SHA1 Message Date
shadcn
d718a8045f Merge pull request #10328 from shadcn-ui/changeset-release/main
chore(release): version packages
2026-04-07 22:13:37 +04:00
github-actions[bot]
2c4678c8c8 chore(release): version packages 2026-04-07 17:48:58 +00:00
shadcn
2466a300f4 Merge pull request #10313 from shadcn-ui/shadcn/apply-preset
feat: add apply
2026-04-07 21:47:54 +04:00
shadcn
66fcf1e853 Merge branch 'shadcn/apply-preset' of github.com:shadcn-ui/ui into shadcn/apply-preset 2026-04-07 21:36:15 +04:00
shadcn
5ebd54198d fix 2026-04-07 21:36:09 +04:00
shadcn
3a2d812510 fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-07 21:24:14 +04:00
shadcn
7811557088 fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-07 21:23:59 +04:00
shadcn
575f1602a1 fix 2026-04-07 21:05:08 +04:00
shadcn
ae70ecc2f3 Merge branch 'shadcn/apply-preset' of github.com:shadcn-ui/ui into shadcn/apply-preset 2026-04-07 18:02:20 +04:00
shadcn
42284f4e64 test: update rtl 2026-04-07 18:02:05 +04:00
shadcn
abc65a4871 Merge branch 'main' into shadcn/apply-preset 2026-04-07 16:27:29 +04:00
shadcn
7d5af61468 style: fix code block 2026-04-07 16:11:26 +04:00
shadcn
2badcdc31f Merge pull request #10323 from shadcn-ui/docs/component-composition-sections
docs: add composition section
2026-04-07 15:57:44 +04:00
shadcn
64b8263450 docs: add changelog 2026-04-07 15:49:54 +04:00
shadcn
13b4593f37 fix 2026-04-07 15:49:26 +04:00
shadcn
7dc65da6b2 fix 2026-04-07 15:28:19 +04:00
shadcn
98e56b773c Merge branch 'shadcn/apply-preset' of github.com:shadcn-ui/ui into shadcn/apply-preset 2026-04-07 15:27:56 +04:00
shadcn
7ff9778ff0 fix 2026-04-07 15:27:33 +04:00
shadcn
4af7bbf4ba Merge branch 'main' into docs/component-composition-sections 2026-04-07 15:25:02 +04:00
shadcn
f00a94d9e5 docs: add composition section 2026-04-07 15:23:27 +04:00
shadcn
187ae44fa7 Merge pull request #10318 from shadcn-ui/dependabot/npm_and_yarn/templates/start-monorepo/vite-7.3.2
chore(deps-dev): bump vite from 7.3.1 to 7.3.2 in /templates/start-monorepo
2026-04-07 14:40:02 +04:00
shadcn
034178bf7d Merge pull request #10317 from shadcn-ui/dependabot/npm_and_yarn/templates/react-router-monorepo/vite-7.3.2
chore(deps-dev): bump vite from 7.3.1 to 7.3.2 in /templates/react-router-monorepo
2026-04-07 14:39:46 +04:00
shadcn
4064c78bc7 Merge pull request #10316 from shadcn-ui/dependabot/npm_and_yarn/templates/vite-monorepo/vite-7.3.2
chore(deps-dev): bump vite from 7.3.1 to 7.3.2 in /templates/vite-monorepo
2026-04-07 14:39:28 +04:00
shadcn
943b023b7c Merge pull request #10314 from shadcn-ui/dependabot/npm_and_yarn/vite-7.3.2
chore(deps): bump vite from 7.1.12 to 7.3.2
2026-04-07 14:39:12 +04:00
shadcn
e3d654fd26 fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-07 14:01:23 +04:00
dependabot[bot]
71d0470be1 chore(deps-dev): bump vite in /templates/start-monorepo
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 22:12:00 +00:00
dependabot[bot]
53bbdc738f chore(deps-dev): bump vite in /templates/react-router-monorepo
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 21:45:55 +00:00
dependabot[bot]
97707ec08e chore(deps-dev): bump vite in /templates/vite-monorepo
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 21:45:51 +00:00
dependabot[bot]
b9ce2f10c3 chore(deps): bump vite from 7.1.12 to 7.3.2
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.12 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 20:17:13 +00:00
shadcn
7cb3b13a33 fix 2026-04-06 23:32:48 +04:00
shadcn
e3d2b14911 fix 2026-04-06 23:27:15 +04:00
shadcn
58c9dc2a7e fix 2026-04-06 23:27:09 +04:00
shadcn
3bdf60340d fix 2026-04-06 23:19:37 +04:00
shadcn
c1e29824cd feat: add apply command 2026-04-06 23:14:57 +04:00
shadcn
62f6df75f2 Merge pull request #10310 from shadcn-ui/shadcn/create-cls
fix: page cls
2026-04-06 17:51:50 +04:00
shadcn
62bae86e86 fix: page cls 2026-04-06 17:37:56 +04:00
shadcn
aa69fbf85a Merge pull request #10309 from shadcn-ui/shadcn/create-perf
fix: create page perf
2026-04-06 16:56:13 +04:00
shadcn
8d41295f2c fix: create page perf 2026-04-06 16:45:17 +04:00
shadcn
2b053d916d Merge pull request #10285 from vzkiss/add-flowkit-ui-registry
feat(registry): add @flowkit-ui
2026-04-06 15:22:32 +04:00
shadcn
0d1309f322 Merge branch 'main' of github.com:shadcn-ui/ui 2026-04-06 15:15:59 +04:00
shadcn
c26250dcfe docs: changelog updates 2026-04-06 15:15:42 +04:00
vzkiss
07c5c36be8 Merge branch 'main' into add-flowkit-ui-registry 2026-04-06 08:51:51 +02:00
vzkiss
3c59a0cd95 Merge branch 'main' into add-flowkit-ui-registry 2026-04-05 23:16:10 +02:00
vzkiss
16b7bea50d feat(registry): build @flowkit-ui to registries.json 2026-04-04 01:36:16 +02:00
vzkiss
ccc4caad9c feat(registry): add @flowkit-ui to directory.json 2026-04-04 01:13:04 +02:00
118 changed files with 4542 additions and 1473 deletions

View File

@@ -63,11 +63,7 @@ export default function IndexPage() {
</Button>
</PageActions>
</PageHeader>
<PageNav className="hidden md:flex">
<ExamplesNav className="flex-1 overflow-hidden [&>a:first-child]:text-primary" />
<ThemeSelector className="mr-4 hidden md:flex" />
</PageNav>
<div className="container-wrapper flex-1 section-soft pb-6">
<div className="container-wrapper flex-1 pb-6">
<div className="container overflow-hidden">
<section className="-mx-4 w-[160vw] overflow-hidden rounded-lg border border-border/50 md:hidden md:w-[150vw]">
<Image

View File

@@ -1,6 +1,7 @@
"use client"
import * as React from "react"
import dynamic from "next/dynamic"
import { type RegistryItem } from "shadcn/schema"
import { useIsMobile } from "@/hooks/use-mobile"
@@ -22,7 +23,6 @@ import { FontPicker } from "@/app/(app)/create/components/font-picker"
import { IconLibraryPicker } from "@/app/(app)/create/components/icon-library-picker"
import { MainMenu } from "@/app/(app)/create/components/main-menu"
import { MenuColorPicker } from "@/app/(app)/create/components/menu-picker"
import { ProjectForm } from "@/app/(app)/create/components/project-form"
import { RadiusPicker } from "@/app/(app)/create/components/radius-picker"
import { RandomButton } from "@/app/(app)/create/components/random-button"
import { ResetDialog } from "@/app/(app)/create/components/reset-button"
@@ -32,6 +32,13 @@ import { V0Button } from "@/app/(app)/create/components/v0-button"
import { FONT_HEADING_OPTIONS, FONTS } from "@/app/(app)/create/lib/fonts"
import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params"
// Only visible when user clicks "Create Project".
const ProjectForm = dynamic(() =>
import("@/app/(app)/create/components/project-form").then(
(m) => m.ProjectForm
)
)
export function Customizer({
itemsByBase,
}: {

View File

@@ -1,7 +1,8 @@
"use client"
import * as React from "react"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { Suspense } from "react"
import { useRouter, useSearchParams } from "next/navigation"
type HistoryContextValue = {
canGoBack: boolean
@@ -12,12 +13,28 @@ type HistoryContextValue = {
const HistoryContext = React.createContext<HistoryContextValue | null>(null)
export function HistoryProvider({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
// Reads useSearchParams() in its own Suspense boundary so the
// provider never blanks out children while search params resolve.
function PresetSync({
onPresetChange,
}: {
onPresetChange: (preset: string) => void
}) {
const searchParams = useSearchParams()
const preset = searchParams.get("preset") ?? ""
React.useEffect(() => {
onPresetChange(preset)
}, [preset, onPresetChange])
return null
}
export function HistoryProvider({ children }: { children: React.ReactNode }) {
const router = useRouter()
const [preset, setPreset] = React.useState("")
const entriesRef = React.useRef<string[]>([preset])
const indexRef = React.useRef(0)
const maxIndexRef = React.useRef(0)
@@ -26,6 +43,10 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) {
const [index, setIndex] = React.useState(0)
const [maxIndex, setMaxIndex] = React.useState(0)
const onPresetChange = React.useCallback((nextPreset: string) => {
setPreset(nextPreset)
}, [])
React.useEffect(() => {
if (isNavigatingRef.current) {
isNavigatingRef.current = false
@@ -67,9 +88,10 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) {
} else {
params.delete("preset")
}
const pathname = window.location.pathname
const query = params.toString()
router.replace(query ? `${pathname}?${query}` : pathname)
}, [pathname, router])
}, [router])
const goForward = React.useCallback(() => {
if (indexRef.current >= maxIndexRef.current) {
@@ -88,9 +110,10 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) {
} else {
params.delete("preset")
}
const pathname = window.location.pathname
const query = params.toString()
router.replace(query ? `${pathname}?${query}` : pathname)
}, [pathname, router])
}, [router])
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
@@ -133,7 +156,14 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) {
[canGoBack, canGoForward, goBack, goForward]
)
return <HistoryContext value={value}>{children}</HistoryContext>
return (
<HistoryContext value={value}>
<Suspense>
<PresetSync onPresetChange={onPresetChange} />
</Suspense>
{children}
</HistoryContext>
)
}
export function useHistory() {

View File

@@ -24,10 +24,15 @@ const LocksContext = React.createContext<LocksContextValue | null>(null)
export function LocksProvider({ children }: { children: React.ReactNode }) {
const [locks, setLocks] = React.useState<Set<LockableParam>>(new Set())
const locksRef = React.useRef(locks)
React.useEffect(() => {
locksRef.current = locks
}, [locks])
// Stable callback — reads from ref so it doesn't change on every lock toggle.
const isLocked = React.useCallback(
(param: LockableParam) => locks.has(param),
[locks]
(param: LockableParam) => locksRef.current.has(param),
[]
)
const toggleLock = React.useCallback((param: LockableParam) => {

View File

@@ -41,7 +41,7 @@ import { getPresetCode } from "@/app/(app)/create/lib/preset-code"
import { resolvePresetOverrides } from "@/app/(app)/create/lib/preset-query"
const designSystemSearchParams = {
preset: parseAsString.withDefault("b2D0wqNxT"),
preset: parseAsString.withDefault("b0"),
base: parseAsStringLiteral<BaseName>(BASES.map((b) => b.name)).withDefault(
DEFAULT_CONFIG.base
),

View File

@@ -1,13 +1,22 @@
import { Suspense } from "react"
import { type Metadata } from "next"
import dynamic from "next/dynamic"
import { siteConfig } from "@/lib/config"
import { absoluteUrl } from "@/lib/utils"
import { Skeleton } from "@/styles/base-nova/ui/skeleton"
import { Customizer } from "@/app/(app)/create/components/customizer"
import { PresetHandler } from "@/app/(app)/create/components/preset-handler"
import { Preview } from "@/app/(app)/create/components/preview"
import { WelcomeDialog } from "@/app/(app)/create/components/welcome-dialog"
import { getAllItems } from "@/app/(app)/create/lib/api"
// Only shown on first visit (checks localStorage).
const WelcomeDialog = dynamic(() =>
import("@/app/(app)/create/components/welcome-dialog").then(
(m) => m.WelcomeDialog
)
)
export const metadata: Metadata = {
title: "New Project",
description:
@@ -37,9 +46,7 @@ export const metadata: Metadata = {
},
}
export default async function CreatePage() {
const itemsByBase = await getAllItems()
export default function CreatePage() {
return (
<div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-hidden section-soft [--customizer-width:--spacing(48)] [--gap:--spacing(4)] md:[--gap:--spacing(6)] 2xl:[--customizer-width:--spacing(56)]">
<div
@@ -47,10 +54,21 @@ export default async function CreatePage() {
className="flex min-h-0 flex-1 flex-col gap-(--gap) p-(--gap) pt-[calc(var(--gap)*0.25)] md:flex-row-reverse"
>
<Preview />
<Customizer itemsByBase={itemsByBase} />
<Suspense
fallback={
<Skeleton className="isolate min-h-[151px] w-full self-start rounded-2xl md:h-full md:max-h-full md:min-h-0 md:w-(--customizer-width)" />
}
>
<CustomizerLoader />
</Suspense>
</div>
<PresetHandler />
<WelcomeDialog />
</div>
)
}
async function CustomizerLoader() {
const itemsByBase = await getAllItems()
return <Customizer itemsByBase={itemsByBase} />
}

View File

@@ -10,6 +10,8 @@ import { Button } from "@/styles/radix-nova/ui/button"
export const revalidate = false
export const dynamic = "force-static"
const NUMBER_OF_LATEST_PAGES = 2
export function generateMetadata() {
return {
title: "Changelog",
@@ -34,8 +36,8 @@ export function generateMetadata() {
export default function ChangelogPage() {
const pages = getChangelogPages()
const latestPages = pages.slice(0, 5)
const olderPages = pages.slice(5)
const latestPages = pages.slice(0, NUMBER_OF_LATEST_PAGES)
const olderPages = pages.slice(NUMBER_OF_LATEST_PAGES)
return (
<div
@@ -44,7 +46,7 @@ export default function ChangelogPage() {
>
<div className="flex min-w-0 flex-1 flex-col">
<div className="h-(--top-spacing) shrink-0" />
<div className="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
<div className="mx-auto flex w-full max-w-160 min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<h1 className="scroll-m-24 text-4xl font-semibold tracking-tight sm:text-3xl">

View File

@@ -337,6 +337,44 @@
display: inline-block;
}
/*
* ```text composition trees use box-drawing characters; per-line padding makes
* vertical connectors look broken. rehype-pretty-code sets `data-language` on
* `pre`/`code` (not `language-*` classes). It also sets `code { display: grid }`,
* which can add visible row separation — reset to a normal pre stack for text.
*/
[data-rehype-pretty-code-figure] pre[data-language="text"] code,
[data-rehype-pretty-code-figure] pre[data-language="plaintext"] code,
[data-slot="docs"] pre[data-language="text"] code,
[data-slot="docs"] pre[data-language="plaintext"] code {
display: block !important;
white-space: pre;
line-height: 0.95;
font-family:
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
"Liberation Mono",
"Courier New",
monospace;
font-variant-ligatures: none;
}
[data-rehype-pretty-code-figure] pre[data-language="text"] [data-line],
[data-rehype-pretty-code-figure] pre[data-language="plaintext"] [data-line],
[data-rehype-pretty-code-figure] code[data-language="text"] [data-line],
[data-rehype-pretty-code-figure] code[data-language="plaintext"] [data-line],
[data-slot="docs"] pre[data-language="text"] [data-line],
[data-slot="docs"] pre[data-language="plaintext"] [data-line] {
padding-top: 0;
padding-bottom: 0;
min-height: unset;
line-height: 0.95;
display: block;
}
[data-line] span {
color: var(--shiki-light);

View File

@@ -6,7 +6,7 @@ import { Badge } from "@/registry/new-york-v4/ui/badge"
export function Announcement() {
return (
<Badge asChild variant="secondary" className="bg-muted">
<Link href="/create">
<Link href="/docs/changelog">
Introducing Luma <ArrowRightIcon />
</Link>
</Badge>

View File

@@ -5,7 +5,7 @@ description: Use the shadcn CLI to add components to your project.
## init
Use the `init` command to initialize configuration and dependencies for a new project.
Use the `init` command to initialize configuration and dependencies for an existing project, or create a new project with `--name`.
The `init` command installs dependencies, adds the `cn` util and configures CSS variables for the project.
@@ -21,26 +21,26 @@ Usage: shadcn init [options] [components...]
initialize your project and install dependencies
Arguments:
components name, url or local path to component
components names, url or local path to component
Options:
-t, --template <template> the template to use. (next, vite, start, react-router, laravel, astro)
-b, --base <base> the component library to use. (radix, base)
-p, --preset [name] use a preset configuration. (name, URL, or preset code)
-n, --name <name> the name for the new project.
-d, --defaults use default configuration. (default: false)
-p, --preset [name] use a preset configuration
-y, --yes skip confirmation prompt. (default: true)
-d, --defaults use default configuration: --template=next --preset=nova (default: false)
-f, --force force overwrite of existing configuration. (default: false)
-c, --cwd <cwd> the working directory. defaults to the current directory.
-n, --name <name> the name for the new project.
-s, --silent mute output. (default: false)
--monorepo scaffold a monorepo project.
--no-monorepo skip the monorepo prompt.
--reinstall re-install existing UI components.
--no-reinstall do not re-install existing UI components.
--rtl enable RTL support.
--no-rtl disable RTL support.
--css-variables use css variables for theming. (default: true)
--no-css-variables do not use css variables for theming.
--monorepo scaffold a monorepo project.
--no-monorepo skip the monorepo prompt.
--rtl enable RTL support.
--no-rtl disable RTL support.
--reinstall re-install existing UI components.
--no-reinstall do not re-install existing UI components.
-h, --help display help for command
```
@@ -85,6 +85,34 @@ Options:
---
## apply
Use the `apply` command to apply a preset to an existing project.
```bash
npx shadcn@latest apply --preset a2r6bw
```
**Options**
```bash
Usage: shadcn apply [options] [preset]
apply a preset to an existing project
Arguments:
preset the preset to apply
Options:
--preset <preset> preset configuration to apply
-y, --yes skip confirmation prompt. (default: false)
-c, --cwd <cwd> the working directory. defaults to the current directory.
-s, --silent mute output. (default: false)
-h, --help display help for command
```
---
## view
Use the `view` command to view items from the registry before installing them.

View File

@@ -0,0 +1,37 @@
---
title: March 2026 - Introducing Luma
description: Rounded geometry. Soft elevation. Breathable layouts. Inspired by macOS Tahoe, minus the glass.
date: 2026-03-31
---
Introducing Luma, a new shadcn/ui style. Rounded geometry. Soft elevation. Breathable layouts. Inspired by macOS Tahoe, minus the glass.
<a href="/create">
<Image
src="/images/luma-light.png"
width="2160"
height="1832"
alt="Luma style preview"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/luma-dark.png"
width="2160"
height="1832"
alt="Luma style preview"
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
/>
<span className="sr-only">Try Luma in shadcn/create</span>
</a>
Luma is a new foundation for your next app. It gives components softer surfaces, more open spacing, and a calmer visual rhythm while keeping the same shadcn/ui workflow.
Like the other new styles, Luma goes beyond theming. It changes the geometry, spacing, and feel of the components so your app starts from a different visual baseline.
Available now in [shadcn/create](/create) for both Radix and Base UI.
<Button asChild size="sm">
<Link href="/create?preset=b2D0wqNxT" className="mt-6 no-underline!">
Try Luma
</Link>
</Button>

View File

@@ -0,0 +1,31 @@
---
title: April 2026 - Component Composition
description: Composition sections across component pages—structured trees that help you and your agents build correct UI.
date: 2026-04-06
---
We've added **Composition** sections across the component docs so you can see the correct structure at a glance: what wraps what, which subcomponents belong together, and how to avoid invalid nesting.
```text
Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooter
```
## Why we added this
We've found that **LLMs and coding agents compose elements more reliably** when they can see the full structure: fewer missing wrappers, fewer wrong hierarchies, better matches to the examples.
### Bring docs into your agent
You or your LLM can pull the same component documentation, including composition, usage, and examples, into context from the CLI:
```bash
npx shadcn@latest docs card
```
If you're using the [shadcn/skills](/docs/skills), this is done automatically for you.

View File

@@ -80,6 +80,20 @@ import {
</Accordion>
```
## Composition
Use the following composition to build an `Accordion`:
```text
Accordion
├── AccordionItem
│ ├── AccordionTrigger
│ └── AccordionContent
└── AccordionItem
├── AccordionTrigger
└── AccordionContent
```
## Examples
### Basic

View File

@@ -94,6 +94,23 @@ import {
</AlertDialog>
```
## Composition
Use the following composition to build an `AlertDialog`:
```text
AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent
├── AlertDialogHeader
│ ├── AlertDialogMedia
│ ├── AlertDialogTitle
│ └── AlertDialogDescription
└── AlertDialogFooter
├── AlertDialogCancel
└── AlertDialogAction
```
## Examples
### Basic

View File

@@ -67,6 +67,18 @@ import {
</Alert>
```
## Composition
Use the following composition to build an `Alert`:
```text
Alert
├── Icon
├── AlertTitle
├── AlertDescription
└── AlertAction
```
## Examples
### Basic

View File

@@ -69,6 +69,32 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
</Avatar>
```
## Composition
Use the following composition to build an `Avatar`:
```text
Avatar
├── AvatarImage
├── AvatarFallback
└── AvatarBadge
```
Use the following composition to build an `AvatarGroup`:
```text
AvatarGroup
├── Avatar
│ ├── AvatarImage
│ ├── AvatarFallback
│ └── AvatarBadge
├── Avatar
│ ├── AvatarImage
│ ├── AvatarFallback
│ └── AvatarBadge
└── AvatarGroupCount
```
## Examples
### Basic

View File

@@ -80,6 +80,23 @@ import {
</Breadcrumb>
```
## Composition
Use the following composition to build a `Breadcrumb`:
```text
Breadcrumb
└── BreadcrumbList
├── BreadcrumbItem
│ └── BreadcrumbLink
├── BreadcrumbSeparator
├── BreadcrumbItem
│ └── BreadcrumbLink
├── BreadcrumbSeparator
└── BreadcrumbItem
└── BreadcrumbPage
```
## Examples
### Basic

View File

@@ -66,6 +66,17 @@ import {
</ButtonGroup>
```
## Composition
Use the following composition to build a `ButtonGroup`:
```text
ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupText
```
## Accessibility
- The `ButtonGroup` component has the `role` attribute set to `group`.

View File

@@ -77,6 +77,20 @@ import {
</Card>
```
## Composition
Use the following composition to build a `Card`:
```text
Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooter
```
## Examples
### Size

View File

@@ -85,6 +85,19 @@ import {
</Carousel>
```
## Composition
Use the following composition to build a `Carousel`:
```text
Carousel
├── CarouselContent
│ ├── CarouselItem
│ └── CarouselItem
├── CarouselPrevious
└── CarouselNext
```
## Examples
### Sizes

View File

@@ -73,6 +73,16 @@ import {
</Collapsible>
```
## Composition
Use the following composition to build a `Collapsible`:
```text
Collapsible
├── CollapsibleTrigger
└── CollapsibleContent
```
## Controlled State
Use the `open` and `onOpenChange` props to control the state.

View File

@@ -90,6 +90,62 @@ export function ExampleCombobox() {
}
```
## Composition
### Simple
A single-line input and a flat list (see [Basic](#basic)).
```text
Combobox
├── ComboboxInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
├── ComboboxItem
└── ComboboxItem
```
### With chips
Multi-select with `multiple`, chips, and a chips input (see [Multiple](#multiple)).
```text
Combobox
├── ComboboxChips
│ ├── ComboboxValue
│ │ └── ComboboxChip
│ └── ComboboxChipsInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
├── ComboboxItem
└── ComboboxItem
```
### With groups and collection
Nested items per group using `ComboboxCollection` inside each `ComboboxGroup`, with a separator between groups (see [Groups](#groups)).
```text
Combobox
├── ComboboxInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
├── ComboboxGroup
│ ├── ComboboxLabel
│ └── ComboboxCollection
│ ├── ComboboxItem
│ └── ComboboxItem
├── ComboboxSeparator
└── ComboboxGroup
├── ComboboxLabel
└── ComboboxCollection
├── ComboboxItem
└── ComboboxItem
```
## Custom Items
Use `itemToStringValue` when your items are objects.

View File

@@ -96,6 +96,24 @@ import {
</Command>
```
## Composition
Use the following composition to build a `Command`:
```text
Command
├── CommandInput
└── CommandList
├── CommandEmpty
├── CommandGroup
│ ├── CommandItem
│ └── CommandItem
├── CommandSeparator
└── CommandGroup
├── CommandItem
└── CommandItem
```
## Examples
### Basic

View File

@@ -79,6 +79,37 @@ import {
</ContextMenu>
```
## Composition
Use the following composition to build a `ContextMenu`:
```text
ContextMenu
├── ContextMenuTrigger
└── ContextMenuContent
├── ContextMenuGroup
│ ├── ContextMenuLabel
│ ├── ContextMenuItem
│ └── ContextMenuItem
├── ContextMenuSeparator
├── ContextMenuGroup
│ ├── ContextMenuLabel
│ ├── ContextMenuCheckboxItem
│ └── ContextMenuCheckboxItem
├── ContextMenuSeparator
├── ContextMenuGroup
│ ├── ContextMenuLabel
│ └── ContextMenuRadioGroup
│ ├── ContextMenuRadioItem
│ └── ContextMenuRadioItem
└── ContextMenuSub
├── ContextMenuSubTrigger
└── ContextMenuSubContent
└── ContextMenuGroup
├── ContextMenuItem
└── ContextMenuItem
```
## Examples
### Basic

View File

@@ -58,6 +58,17 @@ export function DatePickerDemo() {
See the [React DayPicker](https://react-day-picker.js.org) documentation for more information.
## Composition
A date picker is built from `Popover` and `Calendar` (there is no `DatePicker` root component):
```text
Popover
├── PopoverTrigger
└── PopoverContent
└── Calendar
```
## Examples
### Basic

View File

@@ -85,6 +85,20 @@ import {
</Dialog>
```
## Composition
Use the following composition to build a `Dialog`:
```text
Dialog
├── DialogTrigger
└── DialogContent
├── DialogHeader
│ ├── DialogTitle
│ └── DialogDescription
└── DialogFooter
```
## Examples
### Custom Close Button

View File

@@ -88,6 +88,20 @@ import {
</Drawer>
```
## Composition
Use the following composition to build a `Drawer`:
```text
Drawer
├── DrawerTrigger
└── DrawerContent
├── DrawerHeader
│ ├── DrawerTitle
│ └── DrawerDescription
└── DrawerFooter
```
## Examples
### Scrollable Content

View File

@@ -92,6 +92,38 @@ import {
</DropdownMenu>
```
## Composition
Use the following composition to build a `DropdownMenu`:
```text
DropdownMenu
├── DropdownMenuTrigger
└── DropdownMenuContent
├── DropdownMenuGroup
│ ├── DropdownMenuLabel
│ ├── DropdownMenuItem
│ └── DropdownMenuItem
├── DropdownMenuSeparator
├── DropdownMenuGroup
│ ├── DropdownMenuLabel
│ ├── DropdownMenuCheckboxItem
│ └── DropdownMenuCheckboxItem
├── DropdownMenuSeparator
├── DropdownMenuGroup
│ ├── DropdownMenuLabel
│ └── DropdownMenuRadioGroup
│ ├── DropdownMenuRadioItem
│ └── DropdownMenuRadioItem
└── DropdownMenuSub
├── DropdownMenuSubTrigger
└── DropdownMenuSubContent
└── DropdownMenuGroup
├── DropdownMenuLabel
├── DropdownMenuItem
└── DropdownMenuItem
```
## Examples
### Basic

View File

@@ -75,6 +75,19 @@ import {
</Empty>
```
## Composition
Use the following composition to build an `Empty` state:
```text
Empty
├── EmptyHeader
│ ├── EmptyMedia
│ ├── EmptyTitle
│ └── EmptyDescription
└── EmptyContent
```
## Examples
### Outline

View File

@@ -87,6 +87,56 @@ import {
</FieldSet>
```
## Composition
### Field
A single control with label, helper text, and validation.
```text
Field
├── FieldLabel
├── Input / Textarea / Switch / Select
├── FieldDescription
└── FieldError
```
### FieldGroup
Related fields in one group. Use `FieldSeparator` between sections when needed.
```text
FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
├── FieldSeparator
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Select
```
### FieldSet
Semantic grouping with a legend and description, usually containing a `FieldGroup`.
```text
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Select
```
## Anatomy
The `Field` family is designed for composing accessible forms. A typical field is structured as follows:

View File

@@ -75,6 +75,16 @@ import {
</HoverCard>
```
## Composition
Use the following composition to build a `HoverCard`:
```text
HoverCard
├── HoverCardTrigger
└── HoverCardContent
```
## Trigger Delays
Use `delay` and `closeDelay` on the trigger to control when the card opens and

View File

@@ -71,6 +71,18 @@ import {
</InputGroup>
```
## Composition
Use the following composition to build an `InputGroup`:
```text
InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupText
```
## Align
Use the `align` prop on `InputGroupAddon` to position the addon relative to the input.

View File

@@ -82,6 +82,27 @@ import {
</InputOTP>
```
## Composition
Use the following composition to build an `InputOTP`:
```text
InputOTP
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
└── InputOTPGroup
├── InputOTPSlot
└── InputOTPSlot
```
## Pattern
Use the `pattern` prop to define a custom pattern for the OTP input.

View File

@@ -73,6 +73,22 @@ import {
</Item>
```
## Composition
Use the following composition to build an `Item`:
```text
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooter
```
## Item vs Field
Use `Field` if you need to display a form input such as a checkbox, input, radio, or select.

View File

@@ -53,6 +53,17 @@ import { Kbd } from "@/components/ui/kbd"
<Kbd>Ctrl</Kbd>
```
## Composition
Use the following composition to build `Kbd` and `KbdGroup`:
```text
Kbd
KbdGroup
├── Kbd
└── Kbd
```
## Examples
### Group

View File

@@ -88,6 +88,46 @@ import {
</Menubar>
```
## Composition
Use the following composition to build a `Menubar`:
```text
Menubar
├── MenubarMenu
│ ├── MenubarTrigger
│ └── MenubarContent
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarItem
│ │ └── MenubarItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarCheckboxItem
│ │ └── MenubarCheckboxItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ └── MenubarRadioGroup
│ │ ├── MenubarRadioItem
│ │ └── MenubarRadioItem
│ └── MenubarSub
│ ├── MenubarSubTrigger
│ └── MenubarSubContent
│ └── MenubarGroup
│ ├── MenubarLabel
│ ├── MenubarItem
│ └── MenubarItem
└── MenubarMenu
├── MenubarTrigger
└── MenubarContent
└── MenubarGroup
├── MenubarLabel
├── MenubarItem
└── MenubarItem
```
## Examples
### Checkbox

View File

@@ -70,6 +70,34 @@ import {
</NativeSelect>
```
## Composition
### Simple
Options placed directly under `NativeSelect` (no `NativeSelectOptGroup`).
```text
NativeSelect
├── NativeSelectOption
├── NativeSelectOption
├── NativeSelectOption
└── NativeSelectOption
```
### With groups
Use `NativeSelectOptGroup` to organize options into categories.
```text
NativeSelect
├── NativeSelectOptGroup
│ ├── NativeSelectOption
│ └── NativeSelectOption
└── NativeSelectOptGroup
├── NativeSelectOption
└── NativeSelectOption
```
## Examples
### Groups

View File

@@ -82,6 +82,23 @@ import {
</NavigationMenu>
```
## Composition
Use the following composition to build a `NavigationMenu`:
```text
NavigationMenu
├── NavigationMenuList
│ ├── NavigationMenuItem
│ │ ├── NavigationMenuTrigger
│ │ └── NavigationMenuContent
│ │ ├── NavigationMenuLink
│ │ └── NavigationMenuLink
│ └── NavigationMenuItem
│ └── NavigationMenuLink
└── NavigationMenuIndicator
```
## Link Component
Use the `render` prop to compose a custom link component such as Next.js `Link`.

View File

@@ -84,6 +84,23 @@ import {
</Pagination>
```
## Composition
Use the following composition to build a `Pagination`:
```text
Pagination
└── PaginationContent
├── PaginationItem
│ └── PaginationPrevious
├── PaginationItem
│ └── PaginationLink
├── PaginationItem
│ └── PaginationEllipsis
└── PaginationItem
└── PaginationNext
```
## Examples
### Simple

View File

@@ -79,6 +79,16 @@ import {
</Popover>
```
## Composition
Use the following composition to build a `Popover`:
```text
Popover
├── PopoverTrigger
└── PopoverContent
```
## Examples
### Basic

View File

@@ -62,6 +62,33 @@ import { Progress } from "@/components/ui/progress"
<Progress value={33} />
```
## Composition
### With label and value
Use `ProgressLabel` and `ProgressValue` to add a label and value display.
```tsx showLineNumbers
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
;<Progress value={56} className="w-full max-w-sm">
<ProgressLabel>Upload progress</ProgressLabel>
<ProgressValue />
</Progress>
```
```text
Progress
├── ProgressLabel
├── ProgressValue
└── ProgressTrack
└── ProgressIndicator
```
## Examples
### Label

View File

@@ -72,6 +72,16 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
</RadioGroup>
```
## Composition
Use the following composition to build a `RadioGroup`:
```text
RadioGroup
├── RadioGroupItem
└── RadioGroupItem
```
## Examples
### Description

View File

@@ -78,6 +78,17 @@ import {
</ResizablePanelGroup>
```
## Composition
Use the following composition to build a `ResizablePanelGroup`:
```text
ResizablePanelGroup
├── ResizablePanel
├── ResizableHandle
└── ResizablePanel
```
## Examples
### Vertical

View File

@@ -68,6 +68,15 @@ import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
</ScrollArea>
```
## Composition
Use the following composition to build a `ScrollArea`:
```text
ScrollArea
└── ScrollBar
```
## Examples
### Horizontal

View File

@@ -89,6 +89,26 @@ const items = [
</Select>
```
## Composition
Use the following composition to build a `Select`:
```text
Select
├── SelectTrigger
│ └── SelectValue
└── SelectContent
├── SelectGroup
│ ├── SelectLabel
│ ├── SelectItem
│ └── SelectItem
├── SelectSeparator
└── SelectGroup
├── SelectLabel
├── SelectItem
└── SelectItem
```
## Examples
### Align Item With Trigger

View File

@@ -79,6 +79,20 @@ import {
</Sheet>
```
## Composition
Use the following composition to build a `Sheet`:
```text
Sheet
├── SheetTrigger
└── SheetContent
├── SheetHeader
│ ├── SheetTitle
│ └── SheetDescription
└── SheetFooter
```
## Examples
### Side

View File

@@ -55,32 +55,6 @@ npx shadcn@latest add sidebar
</CodeTabs>
## Structure
A `Sidebar` component is composed of the following parts:
- `SidebarProvider` - Handles collapsible state.
- `Sidebar` - The sidebar container.
- `SidebarHeader` and `SidebarFooter` - Sticky at the top and bottom of the sidebar.
- `SidebarContent` - Scrollable content.
- `SidebarGroup` - Section within the `SidebarContent`.
- `SidebarTrigger` - Trigger for the `Sidebar`.
<Image
src="/images/sidebar-structure.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/sidebar-structure-dark.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 hidden w-full overflow-hidden rounded-lg border dark:block"
/>
## Usage
```tsx showLineNumbers title="app/layout.tsx"
@@ -123,6 +97,67 @@ export function AppSidebar() {
}
```
## Composition
Use the following composition to build a `Sidebar` layout:
```text
SidebarProvider
├── Sidebar
│ ├── SidebarHeader
│ ├── SidebarContent
│ │ ├── SidebarGroup
│ │ │ ├── SidebarGroupLabel
│ │ │ ├── SidebarGroupAction
│ │ │ ├── SidebarGroupContent
│ │ │ └── SidebarMenu
│ │ │ ├── SidebarMenuItem
│ │ │ │ ├── SidebarMenuButton
│ │ │ │ ├── SidebarMenuAction
│ │ │ │ └── SidebarMenuBadge
│ │ │ └── SidebarMenuItem
│ │ │ ├── SidebarMenuButton
│ │ │ └── SidebarMenuSub
│ │ │ ├── SidebarMenuSubItem
│ │ │ └── SidebarMenuSubItem
│ │ └── SidebarGroup
│ │ └── SidebarMenu
│ │ ├── SidebarMenuItem
│ │ └── SidebarMenuItem
│ ├── SidebarFooter
│ └── SidebarRail
├── SidebarInset
└── SidebarTrigger
```
## Structure
- **SidebarProvider** — Handles collapsible state and provides sidebar context to child components.
- **Sidebar** — The main collapsible sidebar panel.
- **SidebarHeader** — Sticky at the top; use for branding, titles, or workspace switchers.
- **SidebarFooter** — Sticky at the bottom; use for user menus, settings, or actions.
- **SidebarContent** — Scrollable region between the header and footer.
- **SidebarGroup** — Groups related navigation with optional label, action, and content areas.
- **SidebarMenu** / **SidebarMenuItem** — Menu structure for links, badges, actions, and nested submenus.
- **SidebarRail** — Resize handle for adjusting sidebar width when applicable.
- **SidebarInset** — Wraps main content when using the `inset` variant.
- **SidebarTrigger** — Control that toggles the sidebar open or collapsed.
<Image
src="/images/sidebar-structure.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/sidebar-structure-dark.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 hidden w-full overflow-hidden rounded-lg border dark:block"
/>
## SidebarProvider
The `SidebarProvider` component is used to provide the sidebar context to the `Sidebar` component. You should always wrap your application in a `SidebarProvider` component.

View File

@@ -83,6 +83,33 @@ import {
</Table>
```
## Composition
Use the following composition to build a `Table`:
```text
Table
├── TableCaption
├── TableHeader
│ └── TableRow
│ ├── TableHead
│ ├── TableHead
│ ├── TableHead
│ └── TableHead
├── TableBody
│ ├── TableRow
│ │ ├── TableCell
│ │ ├── TableCell
│ │ ├── TableCell
│ │ └── TableCell
│ └── TableRow
│ ├── TableCell
│ ├── TableCell
│ ├── TableCell
│ └── TableCell
└── TableFooter
```
## Examples
### Footer

View File

@@ -73,6 +73,19 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
</Tabs>
```
## Composition
Use the following composition to build `Tabs`:
```text
Tabs
├── TabsList
│ ├── TabsTrigger
│ └── TabsTrigger
├── TabsContent
└── TabsContent
```
## Examples
### Line

View File

@@ -66,6 +66,16 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
</ToggleGroup>
```
## Composition
Use the following composition to build a `ToggleGroup`:
```text
ToggleGroup
├── ToggleGroupItem
└── ToggleGroupItem
```
## Examples
### Outline

View File

@@ -109,6 +109,16 @@ import {
</Tooltip>
```
## Composition
Use the following composition to build a `Tooltip`:
```text
Tooltip
├── TooltipTrigger
└── TooltipContent
```
## Examples
### Side

View File

@@ -80,6 +80,20 @@ import {
</Accordion>
```
## Composition
Use the following composition to build an `Accordion`:
```text
Accordion
├── AccordionItem
│ ├── AccordionTrigger
│ └── AccordionContent
└── AccordionItem
├── AccordionTrigger
└── AccordionContent
```
## Examples
### Basic

View File

@@ -94,6 +94,23 @@ import {
</AlertDialog>
```
## Composition
Use the following composition to build an `AlertDialog`:
```text
AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent
├── AlertDialogHeader
│ ├── AlertDialogMedia
│ ├── AlertDialogTitle
│ └── AlertDialogDescription
└── AlertDialogFooter
├── AlertDialogCancel
└── AlertDialogAction
```
## Examples
### Basic

View File

@@ -71,6 +71,18 @@ import {
</Alert>
```
## Composition
Use the following composition to build an `Alert`:
```text
Alert
├── Icon
├── AlertTitle
├── AlertDescription
└── AlertAction
```
## Examples
### Basic

View File

@@ -65,6 +65,32 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
</Avatar>
```
## Composition
Use the following composition to build an `Avatar`:
```text
Avatar
├── AvatarImage
├── AvatarFallback
└── AvatarBadge
```
Use the following composition to build an `AvatarGroup`:
```text
AvatarGroup
├── Avatar
│ ├── AvatarImage
│ ├── AvatarFallback
│ └── AvatarBadge
├── Avatar
│ ├── AvatarImage
│ ├── AvatarFallback
│ └── AvatarBadge
└── AvatarGroupCount
```
## Examples
### Basic

View File

@@ -74,6 +74,23 @@ import {
</Breadcrumb>
```
## Composition
Use the following composition to build a `Breadcrumb`:
```text
Breadcrumb
└── BreadcrumbList
├── BreadcrumbItem
│ └── BreadcrumbLink
├── BreadcrumbSeparator
├── BreadcrumbItem
│ └── BreadcrumbLink
├── BreadcrumbSeparator
└── BreadcrumbItem
└── BreadcrumbPage
```
## Examples
### Basic

View File

@@ -66,6 +66,17 @@ import {
</ButtonGroup>
```
## Composition
Use the following composition to build a `ButtonGroup`:
```text
ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupText
```
## Accessibility
- The `ButtonGroup` component has the `role` attribute set to `group`.

View File

@@ -77,6 +77,20 @@ import {
</Card>
```
## Composition
Use the following composition to build a `Card`:
```text
Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooter
```
## Examples
### Size

View File

@@ -85,6 +85,19 @@ import {
</Carousel>
```
## Composition
Use the following composition to build a `Carousel`:
```text
Carousel
├── CarouselContent
│ ├── CarouselItem
│ └── CarouselItem
├── CarouselPrevious
└── CarouselNext
```
## Examples
### Sizes

View File

@@ -77,6 +77,16 @@ import {
</Collapsible>
```
## Composition
Use the following composition to build a `Collapsible`:
```text
Collapsible
├── CollapsibleTrigger
└── CollapsibleContent
```
## Controlled State
Use the `open` and `onOpenChange` props to control the state.

View File

@@ -90,6 +90,62 @@ export function ExampleCombobox() {
}
```
## Composition
### Simple
A single-line input and a flat list (see [Basic](#basic)).
```text
Combobox
├── ComboboxInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
├── ComboboxItem
└── ComboboxItem
```
### With chips
Multi-select with `multiple`, chips, and a chips input (see [Multiple](#multiple)).
```text
Combobox
├── ComboboxChips
│ ├── ComboboxValue
│ │ └── ComboboxChip
│ └── ComboboxChipsInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
├── ComboboxItem
└── ComboboxItem
```
### With groups and collection
Nested items per group using `ComboboxCollection` inside each `ComboboxGroup`, with a separator between groups (see [Groups](#groups)).
```text
Combobox
├── ComboboxInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
├── ComboboxGroup
│ ├── ComboboxLabel
│ └── ComboboxCollection
│ ├── ComboboxItem
│ └── ComboboxItem
├── ComboboxSeparator
└── ComboboxGroup
├── ComboboxLabel
└── ComboboxCollection
├── ComboboxItem
└── ComboboxItem
```
## Custom Items
Use `itemToStringValue` when your items are objects.

View File

@@ -96,6 +96,24 @@ import {
</Command>
```
## Composition
Use the following composition to build a `Command`:
```text
Command
├── CommandInput
└── CommandList
├── CommandEmpty
├── CommandGroup
│ ├── CommandItem
│ └── CommandItem
├── CommandSeparator
└── CommandGroup
├── CommandItem
└── CommandItem
```
## Examples
### Basic

View File

@@ -79,6 +79,37 @@ import {
</ContextMenu>
```
## Composition
Use the following composition to build a `ContextMenu`:
```text
ContextMenu
├── ContextMenuTrigger
└── ContextMenuContent
├── ContextMenuGroup
│ ├── ContextMenuLabel
│ ├── ContextMenuItem
│ └── ContextMenuItem
├── ContextMenuSeparator
├── ContextMenuGroup
│ ├── ContextMenuLabel
│ ├── ContextMenuCheckboxItem
│ └── ContextMenuCheckboxItem
├── ContextMenuSeparator
├── ContextMenuGroup
│ ├── ContextMenuLabel
│ └── ContextMenuRadioGroup
│ ├── ContextMenuRadioItem
│ └── ContextMenuRadioItem
└── ContextMenuSub
├── ContextMenuSubTrigger
└── ContextMenuSubContent
└── ContextMenuGroup
├── ContextMenuItem
└── ContextMenuItem
```
## Examples
### Basic

View File

@@ -56,6 +56,17 @@ export function DatePickerDemo() {
See the [React DayPicker](https://react-day-picker.js.org) documentation for more information.
## Composition
A date picker is built from `Popover` and `Calendar` (there is no `DatePicker` root component):
```text
Popover
├── PopoverTrigger
└── PopoverContent
└── Calendar
```
## Examples
### Basic

View File

@@ -85,6 +85,20 @@ import {
</Dialog>
```
## Composition
Use the following composition to build a `Dialog`:
```text
Dialog
├── DialogTrigger
└── DialogContent
├── DialogHeader
│ ├── DialogTitle
│ └── DialogDescription
└── DialogFooter
```
## Examples
### Custom Close Button

View File

@@ -88,6 +88,20 @@ import {
</Drawer>
```
## Composition
Use the following composition to build a `Drawer`:
```text
Drawer
├── DrawerTrigger
└── DrawerContent
├── DrawerHeader
│ ├── DrawerTitle
│ └── DrawerDescription
└── DrawerFooter
```
## Examples
### Scrollable Content

View File

@@ -92,6 +92,38 @@ import {
</DropdownMenu>
```
## Composition
Use the following composition to build a `DropdownMenu`:
```text
DropdownMenu
├── DropdownMenuTrigger
└── DropdownMenuContent
├── DropdownMenuGroup
│ ├── DropdownMenuLabel
│ ├── DropdownMenuItem
│ └── DropdownMenuItem
├── DropdownMenuSeparator
├── DropdownMenuGroup
│ ├── DropdownMenuLabel
│ ├── DropdownMenuCheckboxItem
│ └── DropdownMenuCheckboxItem
├── DropdownMenuSeparator
├── DropdownMenuGroup
│ ├── DropdownMenuLabel
│ └── DropdownMenuRadioGroup
│ ├── DropdownMenuRadioItem
│ └── DropdownMenuRadioItem
└── DropdownMenuSub
├── DropdownMenuSubTrigger
└── DropdownMenuSubContent
└── DropdownMenuGroup
├── DropdownMenuLabel
├── DropdownMenuItem
└── DropdownMenuItem
```
## Examples
### Basic

View File

@@ -75,6 +75,19 @@ import {
</Empty>
```
## Composition
Use the following composition to build an `Empty` state:
```text
Empty
├── EmptyHeader
│ ├── EmptyMedia
│ ├── EmptyTitle
│ └── EmptyDescription
└── EmptyContent
```
## Examples
### Outline

View File

@@ -87,6 +87,56 @@ import {
</FieldSet>
```
## Composition
### Field
A single control with label, helper text, and validation.
```text
Field
├── FieldLabel
├── Input / Textarea / Switch / Select
├── FieldDescription
└── FieldError
```
### FieldGroup
Related fields in one group. Use `FieldSeparator` between sections when needed.
```text
FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
├── FieldSeparator
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Select
```
### FieldSet
Semantic grouping with a legend and description, usually containing a `FieldGroup`.
```text
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Select
```
## Anatomy
The `Field` family is designed for composing accessible forms. A typical field is structured as follows:

View File

@@ -75,6 +75,16 @@ import {
</HoverCard>
```
## Composition
Use the following composition to build a `HoverCard`:
```text
HoverCard
├── HoverCardTrigger
└── HoverCardContent
```
## Trigger Delays
Use `openDelay` and `closeDelay` on the `HoverCard` to control when the card opens and

View File

@@ -71,6 +71,18 @@ import {
</InputGroup>
```
## Composition
Use the following composition to build an `InputGroup`:
```text
InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupText
```
## Align
Use the `align` prop on `InputGroupAddon` to position the addon relative to the input.

View File

@@ -82,6 +82,27 @@ import {
</InputOTP>
```
## Composition
Use the following composition to build an `InputOTP`:
```text
InputOTP
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
└── InputOTPGroup
├── InputOTPSlot
└── InputOTPSlot
```
## Pattern
Use the `pattern` prop to define a custom pattern for the OTP input.

View File

@@ -73,6 +73,22 @@ import {
</Item>
```
## Composition
Use the following composition to build an `Item`:
```text
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooter
```
## Item vs Field
Use `Field` if you need to display a form input such as a checkbox, input, radio, or select.

View File

@@ -53,6 +53,17 @@ import { Kbd } from "@/components/ui/kbd"
<Kbd>Ctrl</Kbd>
```
## Composition
Use the following composition to build `Kbd` and `KbdGroup`:
```text
Kbd
KbdGroup
├── Kbd
└── Kbd
```
## Examples
### Group

View File

@@ -88,6 +88,46 @@ import {
</Menubar>
```
## Composition
Use the following composition to build a `Menubar`:
```text
Menubar
├── MenubarMenu
│ ├── MenubarTrigger
│ └── MenubarContent
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarItem
│ │ └── MenubarItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarCheckboxItem
│ │ └── MenubarCheckboxItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ └── MenubarRadioGroup
│ │ ├── MenubarRadioItem
│ │ └── MenubarRadioItem
│ └── MenubarSub
│ ├── MenubarSubTrigger
│ └── MenubarSubContent
│ └── MenubarGroup
│ ├── MenubarLabel
│ ├── MenubarItem
│ └── MenubarItem
└── MenubarMenu
├── MenubarTrigger
└── MenubarContent
└── MenubarGroup
├── MenubarLabel
├── MenubarItem
└── MenubarItem
```
## Examples
### Checkbox

View File

@@ -70,6 +70,34 @@ import {
</NativeSelect>
```
## Composition
### Simple
Options placed directly under `NativeSelect` (no `NativeSelectOptGroup`).
```text
NativeSelect
├── NativeSelectOption
├── NativeSelectOption
├── NativeSelectOption
└── NativeSelectOption
```
### With groups
Use `NativeSelectOptGroup` to organize options into categories.
```text
NativeSelect
├── NativeSelectOptGroup
│ ├── NativeSelectOption
│ └── NativeSelectOption
└── NativeSelectOptGroup
├── NativeSelectOption
└── NativeSelectOption
```
## Examples
### Groups

View File

@@ -82,6 +82,23 @@ import {
</NavigationMenu>
```
## Composition
Use the following composition to build a `NavigationMenu`:
```text
NavigationMenu
├── NavigationMenuList
│ ├── NavigationMenuItem
│ │ ├── NavigationMenuTrigger
│ │ └── NavigationMenuContent
│ │ ├── NavigationMenuLink
│ │ └── NavigationMenuLink
│ └── NavigationMenuItem
│ └── NavigationMenuLink
└── NavigationMenuIndicator
```
## Link Component
Use the `asChild` prop to compose a custom link component such as Next.js `Link`.

View File

@@ -84,6 +84,23 @@ import {
</Pagination>
```
## Composition
Use the following composition to build a `Pagination`:
```text
Pagination
└── PaginationContent
├── PaginationItem
│ └── PaginationPrevious
├── PaginationItem
│ └── PaginationLink
├── PaginationItem
│ └── PaginationEllipsis
└── PaginationItem
└── PaginationNext
```
## Examples
### Simple

View File

@@ -79,6 +79,16 @@ import {
</Popover>
```
## Composition
Use the following composition to build a `Popover`:
```text
Popover
├── PopoverTrigger
└── PopoverContent
```
## Examples
### Basic

View File

@@ -72,6 +72,16 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
</RadioGroup>
```
## Composition
Use the following composition to build a `RadioGroup`:
```text
RadioGroup
├── RadioGroupItem
└── RadioGroupItem
```
## Examples
### Description

View File

@@ -78,6 +78,17 @@ import {
</ResizablePanelGroup>
```
## Composition
Use the following composition to build a `ResizablePanelGroup`:
```text
ResizablePanelGroup
├── ResizablePanel
├── ResizableHandle
└── ResizablePanel
```
## Examples
### Vertical

View File

@@ -68,6 +68,15 @@ import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
</ScrollArea>
```
## Composition
Use the following composition to build a `ScrollArea`:
```text
ScrollArea
└── ScrollBar
```
## Examples
### Horizontal

View File

@@ -81,6 +81,26 @@ import {
</Select>
```
## Composition
Use the following composition to build a `Select`:
```text
Select
├── SelectTrigger
│ └── SelectValue
└── SelectContent
├── SelectGroup
│ ├── SelectLabel
│ ├── SelectItem
│ └── SelectItem
├── SelectSeparator
└── SelectGroup
├── SelectLabel
├── SelectItem
└── SelectItem
```
## Examples
### Align Item With Trigger

View File

@@ -79,6 +79,20 @@ import {
</Sheet>
```
## Composition
Use the following composition to build a `Sheet`:
```text
Sheet
├── SheetTrigger
└── SheetContent
├── SheetHeader
│ ├── SheetTitle
│ └── SheetDescription
└── SheetFooter
```
## Examples
### Side

View File

@@ -55,32 +55,6 @@ npx shadcn@latest add sidebar
</CodeTabs>
## Structure
A `Sidebar` component is composed of the following parts:
- `SidebarProvider` - Handles collapsible state.
- `Sidebar` - The sidebar container.
- `SidebarHeader` and `SidebarFooter` - Sticky at the top and bottom of the sidebar.
- `SidebarContent` - Scrollable content.
- `SidebarGroup` - Section within the `SidebarContent`.
- `SidebarTrigger` - Trigger for the `Sidebar`.
<Image
src="/images/sidebar-structure.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/sidebar-structure-dark.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 hidden w-full overflow-hidden rounded-lg border dark:block"
/>
## Usage
```tsx showLineNumbers title="app/layout.tsx"
@@ -123,6 +97,67 @@ export function AppSidebar() {
}
```
## Composition
Use the following composition to build a `Sidebar` layout:
```text
SidebarProvider
├── Sidebar
│ ├── SidebarHeader
│ ├── SidebarContent
│ │ ├── SidebarGroup
│ │ │ ├── SidebarGroupLabel
│ │ │ ├── SidebarGroupAction
│ │ │ ├── SidebarGroupContent
│ │ │ └── SidebarMenu
│ │ │ ├── SidebarMenuItem
│ │ │ │ ├── SidebarMenuButton
│ │ │ │ ├── SidebarMenuAction
│ │ │ │ └── SidebarMenuBadge
│ │ │ └── SidebarMenuItem
│ │ │ ├── SidebarMenuButton
│ │ │ └── SidebarMenuSub
│ │ │ ├── SidebarMenuSubItem
│ │ │ └── SidebarMenuSubItem
│ │ └── SidebarGroup
│ │ └── SidebarMenu
│ │ ├── SidebarMenuItem
│ │ └── SidebarMenuItem
│ ├── SidebarFooter
│ └── SidebarRail
├── SidebarInset
└── SidebarTrigger
```
## Structure
- **SidebarProvider** — Handles collapsible state and provides sidebar context to child components.
- **Sidebar** — The main collapsible sidebar panel.
- **SidebarHeader** — Sticky at the top; use for branding, titles, or workspace switchers.
- **SidebarFooter** — Sticky at the bottom; use for user menus, settings, or actions.
- **SidebarContent** — Scrollable region between the header and footer.
- **SidebarGroup** — Groups related navigation with optional label, action, and content areas.
- **SidebarMenu** / **SidebarMenuItem** — Menu structure for links, badges, actions, and nested submenus.
- **SidebarRail** — Resize handle for adjusting sidebar width when applicable.
- **SidebarInset** — Wraps main content when using the `inset` variant.
- **SidebarTrigger** — Control that toggles the sidebar open or collapsed.
<Image
src="/images/sidebar-structure.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/sidebar-structure-dark.png"
width="716"
height="420"
alt="Sidebar Structure"
className="mt-6 hidden w-full overflow-hidden rounded-lg border dark:block"
/>
## SidebarProvider
The `SidebarProvider` component is used to provide the sidebar context to the `Sidebar` component. You should always wrap your application in a `SidebarProvider` component.

View File

@@ -83,6 +83,33 @@ import {
</Table>
```
## Composition
Use the following composition to build a `Table`:
```text
Table
├── TableCaption
├── TableHeader
│ └── TableRow
│ ├── TableHead
│ ├── TableHead
│ ├── TableHead
│ └── TableHead
├── TableBody
│ ├── TableRow
│ │ ├── TableCell
│ │ ├── TableCell
│ │ ├── TableCell
│ │ └── TableCell
│ └── TableRow
│ ├── TableCell
│ ├── TableCell
│ ├── TableCell
│ └── TableCell
└── TableFooter
```
## Examples
### Footer

View File

@@ -73,6 +73,19 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
</Tabs>
```
## Composition
Use the following composition to build `Tabs`:
```text
Tabs
├── TabsList
│ ├── TabsTrigger
│ └── TabsTrigger
├── TabsContent
└── TabsContent
```
## Examples
### Line

View File

@@ -66,6 +66,16 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
</ToggleGroup>
```
## Composition
Use the following composition to build a `ToggleGroup`:
```text
ToggleGroup
├── ToggleGroupItem
└── ToggleGroupItem
```
## Examples
### Outline

View File

@@ -109,6 +109,16 @@ import {
</Tooltip>
```
## Composition
Use the following composition to build a `Tooltip`:
```text
Tooltip
├── TooltipTrigger
└── TooltipContent
```
## Examples
### Side

View File

@@ -56,7 +56,7 @@
"input-otp": "^1.4.2",
"jotai": "^2.15.0",
"little-date": "^1.0.0",
"lodash": "^4.18.1",
"lodash": "^4.17.23",
"lru-cache": "^11.2.4",
"lucide-react": "0.474.0",
"motion": "^12.12.1",
@@ -76,7 +76,7 @@
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"server-only": "^0.0.1",
"shadcn": "4.1.2",
"shadcn": "4.2.0",
"shiki": "^1.10.1",
"sonner": "^2.0.0",
"swr": "^2.3.6",

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -1018,5 +1018,11 @@
"homepage": "https://mksingh.dev/docs",
"url": "https://mksingh.dev/r/{name}.json",
"description": "A personal registry of production-ready ShadCN components and utilities. Everything is built to drop into your existing ShadCN project with no extra setup."
},
{
"name": "@flowkit-ui",
"homepage": "https://flowkit-ui.vzkiss.com",
"url": "https://flowkit-ui.vzkiss.com/r/{name}.json",
"description": "Opinionated, accessible components on Base UI and shadcn-style primitives — starting with a Creatable Combobox."
}
]

File diff suppressed because one or more lines are too long

View File

@@ -69,7 +69,7 @@
"tailwindcss": "^3.4.18",
"tsx": "^4.1.4",
"turbo": "^1.9.9",
"vite": "^7.1.12",
"vite": "^7.3.2",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^2.1.9"
},

View File

@@ -1,5 +1,11 @@
# @shadcn/ui
## 4.2.0
### Minor Changes
- [#10313](https://github.com/shadcn-ui/ui/pull/10313) [`c1e29824cd7a6809448e45b6b7fe8f7be71ecb0f`](https://github.com/shadcn-ui/ui/commit/c1e29824cd7a6809448e45b6b7fe8f7be71ecb0f) Thanks [@shadcn](https://github.com/shadcn)! - add shadcn apply command
## 4.1.2
### Patch Changes

View File

@@ -20,6 +20,16 @@ The `init` command installs dependencies, adds the `cn` util, configures Tailwin
npx shadcn init
```
## apply
Use the `apply` command to apply a preset to an existing project.
The `apply` command overwrites the current preset configuration, reinstalls detected UI components, and updates fonts and CSS variables to match the new preset.
```bash
npx shadcn apply --preset a2r6bw
```
## add
Use the `add` command to add components to your project.

View File

@@ -1,6 +1,6 @@
{
"name": "shadcn",
"version": "4.1.2",
"version": "4.2.0",
"description": "Add components to your apps.",
"publishConfig": {
"access": "public"

View File

@@ -0,0 +1,49 @@
import { REGISTRY_URL } from "@/src/registry/constants"
import { describe, expect, it } from "vitest"
import { resolveApplyInitUrl } from "./apply"
const SHADCN_URL = REGISTRY_URL.replace(/\/r\/?$/, "")
describe("resolveApplyInitUrl", () => {
it("should include the inferred template for preset codes", () => {
const initUrl = resolveApplyInitUrl("a0", "base", {
template: "next",
rtl: true,
})
const parsed = new URL(initUrl)
expect(parsed.origin + parsed.pathname).toBe(`${SHADCN_URL}/init`)
expect(parsed.searchParams.get("template")).toBe("next")
expect(parsed.searchParams.get("preset")).toBe("a0")
expect(parsed.searchParams.get("base")).toBe("base")
expect(parsed.searchParams.get("rtl")).toBe("true")
})
it("should include the inferred template for named presets", () => {
const initUrl = resolveApplyInitUrl("lyra", "base", {
template: "next",
rtl: true,
})
const parsed = new URL(initUrl)
expect(parsed.origin + parsed.pathname).toBe(`${SHADCN_URL}/init`)
expect(parsed.searchParams.get("template")).toBe("next")
expect(parsed.searchParams.get("base")).toBe("base")
expect(parsed.searchParams.get("rtl")).toBe("true")
})
it("should keep the current base for raw preset URLs without injecting a template", () => {
const presetUrl = `${SHADCN_URL}/init?base=radix&style=nova&baseColor=neutral&theme=neutral&iconLibrary=lucide&font=inter&rtl=false&menuAccent=subtle&menuColor=default&radius=default`
const initUrl = resolveApplyInitUrl(presetUrl, "base", {
template: "next",
rtl: true,
})
const parsed = new URL(initUrl)
expect(parsed.searchParams.get("template")).toBeNull()
expect(parsed.searchParams.get("track")).toBe("1")
expect(parsed.searchParams.get("base")).toBe("base")
expect(parsed.searchParams.get("rtl")).toBe("true")
})
})

View File

@@ -0,0 +1,309 @@
import path from "path"
import { runInit } from "@/src/commands/init"
import { preFlightApply } from "@/src/preflights/preflight-apply"
import { decodePreset, isPresetCode } from "@/src/preset/preset"
import {
DEFAULT_PRESETS,
promptToOpenPresetBuilder,
resolveCreateUrl,
resolveInitUrl,
resolveRegistryBaseConfig,
} from "@/src/preset/presets"
import { SHADCN_URL } from "@/src/registry/constants"
import { clearRegistryContext } from "@/src/registry/context"
import { registryConfigSchema } from "@/src/registry/schema"
import { isUrl } from "@/src/registry/utils"
import { getTemplateForFramework } from "@/src/templates/index"
import { loadEnvFiles } from "@/src/utils/env-loader"
import * as ERRORS from "@/src/utils/errors"
import { withFileBackup } from "@/src/utils/file-helper"
import { getBase } from "@/src/utils/get-config"
import {
getProjectComponents,
getProjectInfo,
} from "@/src/utils/get-project-info"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { Command } from "commander"
import prompts from "prompts"
import { z } from "zod"
export const applyOptionsSchema = z.object({
cwd: z.string(),
positionalPreset: z.string().optional(),
preset: z.string().optional(),
yes: z.boolean(),
silent: z.boolean(),
})
export const apply = new Command()
.name("apply")
.description("apply a preset to an existing project")
.argument("[preset]", "the preset to apply")
.option("--preset <preset>", "preset configuration to apply")
.option("-y, --yes", "skip confirmation prompt.", false)
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.option("-s, --silent", "mute output.", false)
.action(async (positionalPreset, opts) => {
try {
const options = applyOptionsSchema.parse({
...opts,
cwd: path.resolve(opts.cwd),
positionalPreset,
})
const preset = resolveApplyPreset(options)
const preflight = await preFlightApply(options)
if (preflight.errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
logger.break()
logger.error(
`The ${highlighter.info(
"apply"
)} command only works in an existing project.`
)
logger.error(`Run ${highlighter.info(getInitCommand(preset))} first.`)
logger.break()
process.exit(1)
}
if (preflight.errors[ERRORS.MISSING_CONFIG]) {
logger.break()
logger.error(
`No ${highlighter.info("components.json")} found at ${highlighter.info(
options.cwd
)}.`
)
logger.error(`Run ${highlighter.info(getInitCommand(preset))} first.`)
logger.break()
process.exit(1)
}
const existingConfig = preflight.config
if (!existingConfig) {
process.exit(1)
}
const rtl = existingConfig.rtl ?? false
const template = await resolveApplyTemplate(options.cwd)
if (!preset) {
const createUrl = resolveCreateUrl({
command: "init",
template,
base: getBase(existingConfig.style),
rtl,
})
await promptToOpenPresetBuilder({
createUrl,
followUp: `Then run ${highlighter.info(
"shadcn apply --preset <preset>"
)} with the preset code or preset URL from ui.shadcn.com.`,
prompt: !options.yes,
})
process.exit(0)
}
validatePreset(preset)
const reinstallComponents = await getProjectComponents(options.cwd)
if (!options.yes) {
logger.break()
logger.warn(
highlighter.warn(
`Applying a new preset will overwrite existing UI components, fonts, and CSS variables.`
)
)
logger.warn(
`Commit or stash your changes before continuing so you can easily go back.`
)
logger.break()
logger.log(" The following components will be re-installed:")
if (reinstallComponents.length) {
for (let i = 0; i < reinstallComponents.length; i += 8) {
logger.log(` - ${reinstallComponents.slice(i, i + 8).join(", ")}`)
}
} else {
logger.log(" - No installed UI components were detected.")
}
logger.break()
const { proceed } = await prompts({
type: "confirm",
name: "proceed",
message: "Would you like to continue?",
initial: false,
})
if (!proceed) {
logger.break()
process.exit(1)
}
}
await loadEnvFiles(options.cwd)
const currentBase = getBase(existingConfig.style)
const initUrl = resolveApplyInitUrl(preset, currentBase, {
template,
rtl,
})
await withFileBackup(
path.resolve(options.cwd, "components.json"),
async () => {
const {
registryBaseConfig,
installStyleIndex,
url: cleanUrl,
} = await resolveRegistryBaseConfig(initUrl, options.cwd, {
registries: existingConfig.registries as
| z.infer<typeof registryConfigSchema>
| undefined,
})
await runInit({
cwd: options.cwd,
yes: true,
force: false,
reinstall: true,
defaults: false,
silent: options.silent,
isNewProject: false,
cssVariables: true,
installStyleIndex,
registryBaseConfig,
existingConfig,
components: [cleanUrl, ...reinstallComponents],
})
},
{
onBackupFailure: () => {
logger.error(
`Could not back up ${highlighter.info(
"components.json"
)}. Aborting.`
)
},
}
)
logger.break()
logger.log("Preset applied successfully.")
logger.break()
} catch (error) {
logger.break()
handleError(error)
} finally {
clearRegistryContext()
}
})
function resolveApplyPreset(options: z.infer<typeof applyOptionsSchema>) {
const positionalPreset = options.positionalPreset?.trim()
const flagPreset = options.preset?.trim()
if (positionalPreset && flagPreset && positionalPreset !== flagPreset) {
logger.error(
`Received two different preset values. Use either the positional preset or ${highlighter.info(
"--preset"
)}, or pass the same value to both.`
)
logger.break()
process.exit(1)
}
return flagPreset ?? positionalPreset
}
function validatePreset(preset: string) {
if (isUrl(preset) || isPresetCode(preset)) {
return
}
const knownPresetNames = Object.keys(DEFAULT_PRESETS)
if (!knownPresetNames.includes(preset)) {
logger.error(
`Invalid preset: ${highlighter.info(
preset
)}.\nUse one of the available presets: ${knownPresetNames.join(", ")} \nor build your own at ${highlighter.info(`${SHADCN_URL}/create`)}`
)
logger.break()
process.exit(1)
}
}
async function resolveApplyTemplate(cwd: string) {
const projectInfo = await getProjectInfo(cwd)
return getTemplateForFramework(projectInfo?.framework.name)
}
export function resolveApplyInitUrl(
preset: string,
currentBase: "radix" | "base",
options: { template?: string; rtl?: boolean } = {}
) {
if (isUrl(preset)) {
const url = new URL(preset)
if (url.pathname === "/init" && preset.startsWith(SHADCN_URL)) {
url.searchParams.set("track", "1")
}
url.searchParams.set("base", currentBase)
url.searchParams.set("rtl", String(options.rtl ?? false))
return url.toString()
}
if (isPresetCode(preset)) {
const decoded = decodePreset(preset)
if (!decoded) {
logger.error(`Invalid preset code: ${highlighter.info(preset)}`)
logger.break()
process.exit(1)
}
return resolveInitUrl(
{
...decoded,
base: currentBase,
rtl: options.rtl ?? false,
},
{ preset, template: options.template }
)
}
const resolvedPreset = DEFAULT_PRESETS[preset as keyof typeof DEFAULT_PRESETS]
return resolveInitUrl(
{
...resolvedPreset,
base: currentBase,
rtl: options.rtl ?? resolvedPreset.rtl,
},
{ template: options.template }
)
}
function quoteShellArg(value: string) {
return /[^A-Za-z0-9_./:-]/.test(value) ? JSON.stringify(value) : value
}
function getInitCommand(preset?: string) {
if (!preset) {
return "shadcn init"
}
return `shadcn init --preset ${quoteShellArg(preset)}`
}

Some files were not shown because too many files have changed in this diff Show More