# Introduction (/docs) ## Do you? Building something different always comes with some friction. It requires a bit more work, more intention, sometimes even going against the grain. But that's what makes it worth it. Like cooking with your own spices, the end result is richer and more satisfying because it tastes uniquely yours. This is not a component library in the traditional sense. It's a collection of components built from our team's experience, and we are distributing them using the 'shadcn mode'. It was built for our team, but you can use it too. **What do you mean by not a component library?** I mean you own the code. You can decide how the components are built and styled. You can copy and paste the code into your project and customize it to your needs. ## FAQ ### How is this different from other component libraries? Just the taste. It's one more design system for the web. ### How do I pull upstream updates? The `@aura-design/cli init` command is a wrapper around the original `shadcn/ui` CLI. It includes some fixtures to adapt the components to the Aura Design System configuration. When you run `init`, it will ask you if you want to overwrite the existing components. You can say yes to get the latest updates. You can also manually copy and paste the code from the [GitHub repository](https://github.com/garitma/aura-design-system). ### What is the `@aura-design/cli init` command? The `init` command from `@aura-design/cli` is a wrapper around the original `shadcn/ui` CLI. It has been adapted with specific fixtures to seamlessly integrate with the Aura Design System's configuration. This ensures that when you initialize components, they are already set up to work within the Aura ecosystem, saving you time and effort in manual configuration. # Installation (/docs/installation) To install, simply run the Aura Design CLI. ```bash pnpm dlx @aura-design/cli@latest init ``` This command will automatically execute the original shadcn/ui CLI under the hood, and then apply all necessary tokens and configuration fixtures specific to the Aura Design System. You don't need to manually configure anything — just run the command below and your project will be set up with Aura's tokens and settings seamlessly. > **Note:** There isn't a custom CLI command to add *all* components at once. If you want to install all available Aura components in one shot, you can use the list at [https://auradesignsystem.com/all.txt](https://auradesignsystem.com/all.txt). This URL always contains an up-to-date, newline-delimited list of all registry component names. # Namespaces (/docs/namespace) ## What is a Namespace? In the context of our design system's CLI, a namespace is a unique prefix used to identify a specific source of components. Think of it as a label for a component "store" or library. This system is built to be flexible. It allows you to pull components from multiple different sources, such as your own internal registry, a third-party registry (like Radix UI), or the default design system registry. The namespace is what tells the CLI where to look for the component you're requesting. ## Why Use Namespaces? * **Clarity**: When you run a command like add `@aura/button`, it's perfectly clear that you want the "button" component specifically from the "@aura" source. * **Flexibility**: It prevents naming conflicts. You could have a "button" in your internal `@aura` registry and a different "button" in another registry (e.g., `@acme/button`), and the CLI can tell them apart. * **Targeting**: It allows the CLI to look up the correct URL for fetching the component's metadata, which is defined in your `components.json` configuration file. Your components.json (or similar configuration file) includes this registries object: ```json { "registries": { "@aura": "https://auradesignsystem.com/r/{name}.json" } } ``` Let's break this down: * `registries:` This is the main object that holds all your component sources. * `@aura`: This is the namespace. It's the unique prefix you will use in the CLI. * "[https://auradesignsystem.com/r/\{name}.json](https://auradesignsystem.com/r/\{name}.json)": This is the URL endpoint for that namespace. It's a template that tells the CLI how to fetch information about a component. The `{name}` part is a placeholder that will be replaced by the component name you request. ### How it Works in Practice Here is the step-by-step workflow when you want to add a component: # Registry (/docs/registry) ## What is a Registry? A component registry is a system used to store, manage, and distribute individual pieces of our design system, such as components, utilities, or styles. Unlike a traditional component library published as a single package on npm (e.g., `material-ui` or `chakra-ui`), a registry holds the source code for each component separately. When you "install" a component, you are not adding a new dependency to your `node_modules`. Instead, you are **copying the actual source code** directly into your project. ## Why Use a Registry? This registry-based approach provides several key advantages over traditional libraries: * **Full Ownership & Control:** Because the component code lives in your codebase (e.g., in a `/components/ui` folder), you have complete control. You can modify, adapt, and restyle any component to fit your specific needs without waiting for updates or fighting with library opinions. * **No "Black Box":** You can see exactly what the code is doing, making it easier to debug, customize, and understand. * **Pick What You Need:** You only add the components you are actively using. This prevents bloating your application with unused code that often comes bundled in a monolithic library. * **Framework Agnostic (by design):** The components are designed to be copied and pasted. They are not tied to a specific version of our library and don't create complex dependency chains. You own the code and its dependencies. * **Easy Updates:** While you own the code, our CLI tool makes it easy to check for updates to a component and "diff" the changes, allowing you to decide whether to incorporate them. ## How to Consume the Registry Interaction with the registry is handled through our Command Line Interface (CLI). This tool is responsible for initializing your project and fetching component code. # Taste (/docs/taste) import { ThemeColorSwitcher } from "@/components/ThemeColorSwitcher" The following sections outline our approach to foundational design tokens that give Aura its distinct identity. ## Spacing A unit of space in this system is 13px. Spacing tokens are defined in increments of 1 or 0.5 units, such as 1 (13px), 1.5 (19.5px), 2 (26px), 2.5 (32.5px), 3 (39px), 3.5 (45.5px), and 4 (52px). Example spacing scale: | Token | Value | | ----- | ------ | | 1 | 13px | | 1.5 | 19.5px | | 2 | 26px | | 2.5 | 32.5px | | 3 | 39px | | 3.5 | 45.5px | | 4 | 52px | In CSS, you might define these as: ```css @theme inline { --spacing: 13px; } ``` Use these variables for margin, padding, gaps, etc., to maintain consistent spacing across your design system. ## Colors To create a custom color palette, follow these steps: * Open the color mixer * Move to light mode * Input your Accent color (e.g., `#0000b3` for blue). * Input your Gray color (e.g., `#F0EFEB` for gray). * Input your Background color (e.g., `#fafafa` for white). * Download the generated CSS variables. Once you have your color scales, integrate them into your CSS file. Define the colors as CSS variables under the @theme inline layer, prefixed with color-accent. **Naming Logic:** `color-{purpose}-{value}` (e.g., `color-accent-9` for primary brand color). ```css @import "tailwindcss"; @theme inline { --spacing: 13px; --font-sans: Inter, sans-serif; --info: #e8ebfe; --info-contrast: #0927ec; --success: #e8fef7; --success-contrast: #045d3c; --danger: #feefe8; --danger-contrast: #8e3106; --warning: #fefbe8; --warning-contrast: #5d5104; --radius-sm: calc(var(--spacing) * 0.5); --radius-md: calc(var(--spacing) * 1); --radius-lg: calc(var(--spacing) * 1.5); --radius-xl: calc(var(--spacing) * 2); --aura-loader: var(--aura-loader); --color-warning-contrast: var(--warning-contrast); --color-warning: var(--warning); --color-danger-contrast: var(--danger-contrast); --color-danger: var(--danger); --color-success-contrast: var(--success-contrast); --color-success: var(--success); --color-info-contrast: var(--info-contrast); --color-info: var(--info); --secundary-foreground: var(--secundary-foreground); --secundary: var(--secundary); --primary-foreground: var(--primary-foreground); --primary: var(--primary); --color-gray-track: var(--gray-track); --color-gray-indicator: var(--gray-indicator); --color-gray-surface: var(--gray-surface); --color-gray-contrast: var(--gray-contrast); --color-gray-a12: var(--gray-a12); --color-gray-a11: var(--gray-a11); --color-gray-a10: var(--gray-a10); --color-gray-a9: var(--gray-a9); --color-gray-a8: var(--gray-a8); --color-gray-a7: var(--gray-a7); --color-gray-a6: var(--gray-a6); --color-gray-a5: var(--gray-a5); --color-gray-a4: var(--gray-a4); --color-gray-a3: var(--gray-a3); --color-gray-a2: var(--gray-a2); --color-gray-a1: var(--gray-a1); --color-gray-12: var(--gray-12); --color-gray-11: var(--gray-11); --color-gray-10: var(--gray-10); --color-gray-9: var(--gray-9); --color-gray-8: var(--gray-8); --color-gray-7: var(--gray-7); --color-gray-6: var(--gray-6); --color-gray-5: var(--gray-5); --color-gray-4: var(--gray-4); --color-gray-3: var(--gray-3); --color-gray-2: var(--gray-2); --color-gray-1: var(--gray-1); --color-accent-track: var(--accent-track); --color-accent-indicator: var(--accent-indicator); --color-accent-surface: var(--accent-surface); --color-accent-contrast: var(--accent-contrast); --color-accent-a12: var(--accent-a12); --color-accent-a11: var(--accent-a11); --color-accent-a10: var(--accent-a10); --color-accent-a9: var(--accent-a9); --color-accent-a8: var(--accent-a8); --color-accent-a7: var(--accent-a7); --color-accent-a6: var(--accent-a6); --color-accent-a5: var(--accent-a5); --color-accent-a4: var(--accent-a4); --color-accent-a3: var(--accent-a3); --color-accent-a2: var(--accent-a2); --color-accent-a1: var(--accent-a1); --color-accent-12: var(--accent-12); --color-accent-11: var(--accent-11); --color-accent-10: var(--accent-10); --color-accent-9: var(--accent-9); --color-accent-8: var(--accent-8); --color-accent-7: var(--accent-7); --color-accent-6: var(--accent-6); --color-accent-5: var(--accent-5); --color-accent-4: var(--accent-4); --color-accent-3: var(--accent-3); --color-accent-2: var(--accent-2); --color-accent-1: var(--accent-1); } :root { /* Accent color scale */ --accent-1: #faf9fc; --accent-2: #f7f4fc; --accent-3: #f1eafe; --accent-4: #ebdeff; --accent-5: #e2d1fe; --accent-6: #d7c0fa; --accent-7: #c9a9f5; --accent-8: #b78cef; --accent-9: #964ce1; --accent-10: #8841ce; --accent-11: #7d35c2; --accent-12: #421a68; --accent-a1: #7d3dfc04; --accent-a2: #6f1afc09; --accent-a3: #ece1ffaa; --accent-a4: #ebdeff; --accent-a5: #d5bcffaa; --accent-a6: #6202f43d; --accent-a7: #6201e754; --accent-a8: #6202df72; --accent-a9: #6b02d6b3; --accent-a10: #6102bfbe; --accent-a11: #5c01b3ca; --accent-a12: #2d0057e5; --accent-contrast: #fff; --accent-surface: #f6f3fdcc; --accent-indicator: #964ce1; --accent-track: #964ce1; /* Gray color scale */ --gray-1: #f6f9ff; --gray-2: #f0f6ff; --gray-3: #e3ecff; --gray-4: #d7e4ff; --gray-5: #ccdcff; --gray-6: #c2d4ff; --gray-7: #b4c8ff; --gray-8: #99b3ff; --gray-9: #6d84d5; --gray-10: #647ac5; --gray-11: #4b5c9a; --gray-12: #121b48; --gray-a1: #f6f9ff; --gray-a2: #f0f6ff; --gray-a3: #e3ecff; --gray-a4: #d7e4ff; --gray-a5: #ccdcff; --gray-a6: #c2d4ff; --gray-a7: #b4c8ff; --gray-a8: #99b3ff; --gray-a9: #0029b791; --gray-a10: #0025a19a; --gray-a11: #021a72b4; --gray-a12: #000a3aed; --gray-contrast: #ffffff; --gray-surface: #ffffffcc; --gray-indicator: #6d84d5; --gray-track: #6d84d5; --primary: var(--accent-9); --primary-foreground: var(--accent-contrast); --secundary: var(--accent-8); --secundary-foreground: var(--accent-contrast); /* Aura Design System - Root Tokens */ --aura: 13px; --aura-font-stack: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; --aura-font-quotes: serif; /* Aura Background & Text - Mapped to gray scale */ --aura-bg: var(--gray-1); --aura-text-primary: var(--accent-12); --aura-text-primary-inverse: var(--gray-contrast); --aura-accents-primary: var(--accent-9); --aura-accents-secondary: var(--accent-9); /* Aura Component Tokens - Mapped to 12-step system */ --aura-input-radius: 6.5px; --aura-button-radius: 6.5px; --aura-input-bg: var(--accent-2); --aura-input-placeholder-color: var(--gray-11); --aura-radius: 0.5rem; --aura-opacity: 0.5; --aura-outline: var(--accent-9) solid 2px; --aura-button-hover: var(--accent-11); --aura-link: var(--gray-12); --aura-link-hover: var(--accent-2); --aura-selector: var(--accent-surface); --aura-loader: var(--primary); --aura-skeleton-start: var(--gray-8); --aura-skeleton-end: var(--gray-5); --radius: 0.5rem; } @media (prefers-color-scheme: dark) { :root { /* Accent color scale */ --accent-1: #17111e; --accent-2: #1c1527; --accent-3: #2d1b43; --accent-4: #3b1f58; --accent-5: #452767; --accent-6: #513176; --accent-7: #643f8f; --accent-8: #8152b8; --accent-9: #964ce1; --accent-10: #8151b7; --accent-11: #ce9dff; --accent-12: #e9d9ff; --accent-a1: #f600000c; --accent-a2: #fc3f0011; --accent-a3: #fd53da23; --accent-a4: #e64ffc37; --accent-a5: #d35cfd49; --accent-a6: #cd69fd5b; --accent-a7: #c671fd79; --accent-a8: #bc72ffaa; --accent-a9: #ac55ffdb; --accent-a10: #bd71fea9; --accent-a11: #ce9dff; --accent-a12: #e9d9ff; --accent-contrast: #fff; --accent-surface: #2c182480; --accent-indicator: #964ce1; --accent-track: #964ce1; /* Gray color scale */ --gray-1: #0c122a; --gray-2: #131931; --gray-3: #182043; --gray-4: #1b2554; --gray-5: #1f2b62; --gray-6: #243275; --gray-7: #2e3f8b; --gray-8: #455aa9; --gray-9: #5268b8; --gray-10: #5f75c7; --gray-11: #94afff; --gray-12: #e5eeff; --gray-a1: #0d130106; --gray-a2: #ecf2eb08; --gray-a3: #768eff1d; --gray-a4: #5973fc32; --gray-a5: #5572fd43; --gray-a6: #506dfd5a; --gray-a7: #5675ff74; --gray-a8: #6c8bff98; --gray-a9: #7593ffaa; --gray-a10: #7d98ffbc; --gray-a11: #94afff; --gray-a12: #e5eeff; --gray-contrast: #ffffff; --gray-surface: rgba(0, 0, 0, 0.05); --gray-indicator: #5268b8; --gray-track: #5268b8; } } @layer base { @import "../styles/main.css"; html { font-size: 17px; } main { background-color: var(--gray-1); } } @layer components { } ``` ## Typography The typography system is designed to ensure consistent and responsive text scaling across different screen sizes. It uses the CSS `clamp()` function combined with custom properties (CSS variables) to dynamically adjust font sizes based on the viewport width. This approach ensures readability and visual harmony on all devices. ## How It Works The system uses the `clamp()` function, which takes three arguments: * **min**: The minimum font size (in `rem`). * **val**: The preferred font size (in `vw` for responsiveness). * **max**: The maximum font size (in `rem`). The formula ensures that the font size scales fluidly between the `min` and `max` values based on the viewport width. ```css font-size: clamp(var(--min), var(--val), var(--max)); ``` Here’s the complete CSS code for the typography system: ```css body h1, body h2, body h3, body h4, body h5, body h6, body p, body .h1, body .h2, body .h3, body .h4, body .h5, body .h6 { font-size: clamp(var(--min), var(--val), var(--max)); } h1, .h1 { --min: 1.9838rem; --val: 4.029999999999999vw; --max: 3.052rem; } h2, .h2 { --min: 1.58665rem; --val: 4.029999999999999vw; --max: 2.441rem; } h3, .h3 { --min: 1.482rem; --val: 4.029999999999999vw; --max: 2.28rem; } h4, .h4, blockquote { --min: 1.26945rem; --val: 4.029999999999999vw; --max: 1.953rem; } h5, .h5 { --min: 1.17225rem; --val: 4.029999999999999vw; --max: 1.563rem; } h6, .h6 { --min: 1.0625rem; --val: 4.029999999999999vw; --max: 1.25rem; } p, .p { --min: 1rem; --val: 4.029999999999999vw; --max: 1rem; } ``` # Accordion (/docs/components/accordion) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/Accordion"; export function AccordionDemo() { return { return ( Is it accessible? Yes. It adheres to the WAI-ARIA design pattern. Is it styled? Yes. It comes with default styles that matches the other components' aesthetic. Is it animated? Yes. It's animated by default, but you can disable it if you prefer. ); }; ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/accordion ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons radix-ui ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Accordion component into your `components/ui/Accordion.tsx` file. ```tsx "use client"; /** * @description A vertically stacked set of interactive headings that each reveal a section of content. */ import React from "react"; import { Accordion as AccordionRadix } from "radix-ui"; import { ChevronDownIcon } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; function Accordion({ ...props }: React.ComponentProps) { return ; } function AccordionItem({ className, ...props }: React.ComponentProps) { return ( ); } function AccordionTrigger({ className, children, ...props }: React.ComponentProps) { return ( {children} ); } function AccordionContent({ className, children, ...props }: React.ComponentProps) { return (
{children}
); } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; ```
## Usage ### Multiple ```tsx export const Multiple = () => { return ( Is it accessible? Yes. It adheres to the WAI-ARIA design pattern. Is it styled? Yes. It comes with default styles that matches the other components' aesthetic. Is it animated? Yes. It's animated by default, but you can disable it if you prefer. ); }; ``` ### DefaultValue ```tsx export const DefaultValue = () => { return ( Is it accessible? Yes. It adheres to the WAI-ARIA design pattern. Is it styled? Yes. It comes with default styles that matches the other components' aesthetic. Is it animated? Yes. It's animated by default, but you can disable it if you prefer. ); }; ``` ### Disabled ```tsx export const Disabled = () => { return ( Is it accessible? Yes. It adheres to the WAI-ARIA design pattern. Is it styled? Yes. It comes with default styles that matches the other components' aesthetic. Is it animated? Yes. It's animated by default, but you can disable it if you prefer. ); }; ``` ### DisabledItem ```tsx export const DisabledItem = () => { return ( Is it accessible? Yes. It adheres to the WAI-ARIA design pattern. Is it styled? (Disabled) Yes. It comes with default styles that matches the other components' aesthetic. Is it animated? Yes. It's animated by default, but you can disable it if you prefer. ); }; ``` # Alert Dialog (/docs/components/alert-dialog) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/AlertDialog"; import { Button } from "@/components/ui/Button"; export function AlertDialogDemo() { return ( Are you absolutely sure? This action cannot be undone. This will permanently delete your account and remove your data from our servers. ) } ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/alert-dialog ``` ### Manual Install the following dependencies: ```bash pnpm install radix-ui ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the AlertDialog component into your `components/ui/AlertDialog.tsx` file. ```tsx "use client"; /** * @description A modal dialog that interrupts the user with important content and expects a response. */ import * as React from "react"; import { AlertDialog as AlertDialogRadix } from "radix-ui"; import { cn } from "@/utils/class-names"; function AlertDialog({ ...props }: React.ComponentProps) { return ; } function AlertDialogTrigger({ ...props }: React.ComponentProps) { return ( ); } function AlertDialogPortal({ ...props }: React.ComponentProps) { return ; } function AlertDialogOverlay({ className, ...props }: React.ComponentProps) { return ( ); } function AlertDialogContent({ className, ...props }: React.ComponentProps) { return ( ); } function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) { return (
); } function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) { return (
); } function AlertDialogTitle({ className, ...props }: React.ComponentProps) { return ( ); } function AlertDialogDescription({ className, ...props }: React.ComponentProps) { return ( ); } function AlertDialogAction({ className, ...props }: React.ComponentProps) { return ; } function AlertDialogCancel({ className, ...props }: React.ComponentProps) { return ; } export { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, }; ``` # Alert (/docs/components/alert) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { BookmarkIcon } from "@radix-ui/react-icons"; import { Alert, AlertContent, AlertTitle, AlertDescription, AlertIcon, AlertStatus, } from "@/components/ui/Alert"; export function AlertDemo() { return { return ( Alert Title This is a default alert message to demonstrate the Alert component. ); }; ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/alert ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons class-variance-authority ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Alert component into your `components/ui/Alert.tsx` file. ```tsx /** * @description Displays a callout for user attention with contextual feedback messages. */ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { CheckCircledIcon, CrossCircledIcon, ExclamationTriangleIcon, InfoCircledIcon, } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; const alertVariants = cva("flex gap-1 space-y-0.5 p-1 rounded-md border", { variants: { variant: { default: "bg-background text-foreground", info: "bg-info text-info-contrast border-info-contrast border border-info-contrast", success: "bg-success text-success-contrast border-success-contrast border border-success-contrast", warning: "bg-warning text-warning-contrast border-warning-contrast border border-warning-contrast", danger: "bg-danger text-danger-contrast border-danger-contrast border border-danger-contrast", }, }, defaultVariants: { variant: "default", }, }); interface AlertProps extends React.HTMLAttributes, VariantProps {} function Alert({ className, variant, ...props }: AlertProps) { return (
); } function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { return (
); } function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { return
; } function AlertContent({ className, ...props }: React.ComponentProps<"div">) { return
; } function AlertIcon({ className, ...props }: React.ComponentProps<"div">) { return
; } // Local AlertStatus component definition const AlertStatus = ({ title, description, status, icon, }: { title: string; description: string; status: "info" | "success" | "danger" | "warning"; icon?: React.ElementType; }) => { const Icon = icon || { info: InfoCircledIcon, success: CheckCircledIcon, warning: ExclamationTriangleIcon, danger: CrossCircledIcon, }[status]; return ( {title} {description} ); }; export { Alert, AlertTitle, AlertDescription, AlertIcon, AlertContent, AlertStatus, }; ``` ## Usage ### AlertDemo ```tsx export const AlertDemo = () => { return ( Dummy Title This is a dummy description to demonstrate the Alert component. ); }; ``` ### AlertStatusStatusesDemo ```tsx export const AlertStatusStatusesDemo = () => { return (
); }; ``` # Aspect Ratio (/docs/components/aspect-ratio) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { AspectRatio } from "@/components/ui/AspectRatio"; export function AspectRatioDemo() { return (
Landscape photograph by Tobias Tullius
) ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/aspect-ratio ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-aspect-ratio ``` Copy and paste the AspectRatio component into your `components/ui/AspectRatio.tsx` file. ```tsx /** * @description Displays content within a desired ratio. */ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; function AspectRatio({ ...props }: React.ComponentProps) { return ; } export { AspectRatio }; ``` ## Usage ### AspectRatioDemo ```tsx export const AspectRatioDemo = () => (
Landscape photograph by Tobias Tullius
) ``` # Autocomplete (/docs/components/autocomplete) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Autocomplete, AutocompleteEmpty, AutocompleteInput, AutocompleteItem, AutocompleteList, AutocompletePopup, } from "@/components/ui/Autocomplete"; export function AutocompleteDemo() { return { return ( No items found. {(item) => ( {item.label} )} ); } ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/autocomplete ``` ### Manual Install the following dependencies: ```bash pnpm install @base-ui/react @radix-ui/react-icons ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the input component into your `components/ui/Input.tsx` file. ```tsx /** * @description Displays a form input field or a component that looks like an input field. */ import * as React from "react"; import { cn } from "@/utils/class-names"; function Input({ className, ...props }: React.ComponentProps<"input">) { return ; } export { Input }; ``` Copy and paste the scroll area component into your `components/ui/ScrollArea.tsx` file. ```tsx "use client" /** * @description Augments native scroll functionality for custom cross-browser styling. */ import * as React from "react" import { ScrollArea as ScrollAreaPrimitive } from "radix-ui" import { cn } from "@/utils/class-names" const ScrollViewport = ScrollAreaPrimitive.Viewport; const ScrollArea = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} )) ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName const ScrollBar = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, orientation = "vertical", ...props }, ref) => ( )) ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName export { ScrollArea, ScrollBar, ScrollViewport } ``` Copy and paste the Autocomplete component into your `components/ui/Autocomplete.tsx` file. ```tsx "use client"; /** * @description Displays an autocomplete input with a list of options. */ import * as React from "react"; import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete"; import { Cross1Icon, CaretSortIcon } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; import { Input } from "@/components/ui/Input"; import { ScrollArea } from "@/components/ui/ScrollArea"; function Autocomplete({ ...props }: React.ComponentProps) { return ; } function AutocompleteInput({ className, showTrigger = false, showClear = false, startAddon, size, ...props }: Omit & { showTrigger?: boolean; showClear?: boolean; startAddon?: React.ReactNode; size?: "sm" | "default" | "lg" | number; ref?: React.Ref; }) { const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number; return (
{startAddon && ( )} } {...props} /> {showTrigger && ( )} {showClear && ( )}
); } function AutocompletePopup({ className, children, sideOffset = 4, ...props }: AutocompletePrimitive.Popup.Props & { sideOffset?: number; }) { return ( {children} ); } function AutocompleteItem({ className, children, ...props }: AutocompletePrimitive.Item.Props) { return ( {children} ); } function AutocompleteSeparator({ className, ...props }: AutocompletePrimitive.Separator.Props) { return ( ); } function AutocompleteGroup({ ...props }: AutocompletePrimitive.Group.Props) { return ; } function AutocompleteGroupLabel({ className, ...props }: AutocompletePrimitive.GroupLabel.Props) { return ( ); } function AutocompleteEmpty({ className, ...props }: AutocompletePrimitive.Empty.Props) { return ( ); } function AutocompleteRow({ className, ...props }: AutocompletePrimitive.Row.Props) { return ( ); } function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) { return ( ); } function AutocompleteList({ className, ...props }: AutocompletePrimitive.List.Props) { return ( ); } function AutocompleteClear({ className, ...props }: AutocompletePrimitive.Clear.Props) { return ( ); } function AutocompleteStatus({ className, ...props }: AutocompletePrimitive.Status.Props) { return ( ); } function AutocompleteCollection({ ...props }: AutocompletePrimitive.Collection.Props) { return ( ); } function AutocompleteTrigger({ className, ...props }: AutocompletePrimitive.Trigger.Props) { return ( ); } export { Autocomplete, AutocompleteInput, AutocompleteTrigger, AutocompletePopup, AutocompleteItem, AutocompleteSeparator, AutocompleteGroup, AutocompleteGroupLabel, AutocompleteEmpty, AutocompleteValue, AutocompleteList, AutocompleteClear, AutocompleteStatus, AutocompleteRow, AutocompleteCollection, }; ```
# Avatar (/docs/components/avatar) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Avatar, AvatarImage, AvatarFallback, } from "@/components/ui/Avatar"; export function AvatarDemo() { return ( JD ) } ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/avatar ``` ### Manual Install the following dependencies: ```bash pnpm install radix-ui ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Avatar component into your `components/ui/Avatar.tsx` file. ```tsx "use client"; /** * @description An image element with a fallback for representing the user. */ import * as React from "react"; import { Avatar as AvatarRadix } from "radix-ui"; import { cn } from "@/utils/class-names"; function Avatar({ className, ...props }: React.ComponentProps) { return ( ); } function AvatarImage({ className, ...props }: React.ComponentProps) { return ( ); } function AvatarFallback({ className, ...props }: React.ComponentProps) { return ( ); } export { Avatar, AvatarImage, AvatarFallback }; ``` ## Usage ### WithFallback ```tsx export const WithFallback = () => ( AB ) ``` ### FallbackOnly ```tsx export const FallbackOnly = () => ( CD ) ``` ### CustomSize ```tsx export const CustomSize = () => (
SM MD LG
) ``` ### AvatarGroup ```tsx export const AvatarGroup = () => (
U1 U2 U3 +5
) ``` ### WithStatus ```tsx export const WithStatus = () => (
ON
AW
OF
) ``` ### DifferentShapes ```tsx export const DifferentShapes = () => (
RD SQ BX
) ``` ### CustomFallbackStyles ```tsx export const CustomFallbackStyles = () => (
AC GR BL
) ``` # Badge (/docs/components/badge) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/badge ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-slot class-variance-authority ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Badge component into your `components/ui/Badge.tsx` file. ```tsx /** * @description Displays a badge or a component that looks like a badge. */ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/utils/class-names" const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-gray-12 text-gray-1 hover:bg-gray-12/80", secondary: "border-transparent bg-gray-3 text-gray-12 hover:bg-gray-3/80", destructive: "border-transparent bg-danger text-danger-contrast hover:bg-danger/80", outline: "text-gray-12", }, status: { default: "", success: "border-transparent bg-success text-success-contrast hover:bg-success/80", warning: "border-transparent bg-warning text-warning-contrast hover:bg-warning/80", danger: "border-transparent bg-danger text-danger-contrast hover:bg-danger/80", info: "border-transparent bg-info text-info-contrast hover:bg-info/80", } }, defaultVariants: { variant: "default", status: "default", }, } ) export interface BadgeProps extends React.HTMLAttributes, VariantProps { asChild?: boolean } const Badge = React.forwardRef( ({ className, variant, status, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "div" return ( ) } ) Badge.displayName = "Badge" export { Badge, badgeVariants } ``` # Button Group (/docs/components/button-group) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { useState } from "react"; import { ChevronLeftIcon, DotsHorizontalIcon, MinusIcon, PlusIcon, } from "@radix-ui/react-icons"; import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, } from "@/components/ui/ButtonGroup"; import { Button } from "@/components/ui/Button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/DropdownMenu"; import { Input } from "@/components/ui/Input"; import { Label } from "@/components/ui/Label"; import { Select, SelectTrigger, SelectValue, SelectIcon, SelectPortal, SelectContent, SelectViewport, SelectItem, SelectItemText, SelectItemIndicator, } from "@/components/ui/Select"; export function ButtonGroupDemo() { return { const [label, setLabel] = useState("personal"); return (
Mark as Read Archive Snooze Add to Calendar Add to List Label As... Personal Work Other Trash
); }; ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/button-group ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-slot class-variance-authority ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the separator component into your `components/ui/Separator.tsx` file. ```tsx "use client" import * as React from "react" import {Separator as SeparatorPrimitive} from "radix-ui" import { cn } from "@/utils/class-names" function Separator({ className, orientation = "horizontal", decorative = true, ...props }: React.ComponentProps) { return ( ) } export { Separator } ``` Copy and paste the ButtonGroup component into your `components/ui/ButtonGroup.tsx` file. ```tsx import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/utils/class-names" import { Separator } from "@/components/ui/Separator" const buttonGroupVariants = cva( "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2", { variants: { orientation: { horizontal: "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", vertical: "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", }, }, defaultVariants: { orientation: "horizontal", }, } ) function ButtonGroup({ className, orientation, ...props }: React.ComponentProps<"div"> & VariantProps) { return (
) } function ButtonGroupText({ className, asChild = false, ...props }: React.ComponentProps<"div"> & { asChild?: boolean }) { const Comp = asChild ? Slot : "div" return ( ) } function ButtonGroupSeparator({ className, orientation = "vertical", ...props }: React.ComponentProps) { return ( ) } export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants, } ``` ## Usage ### Vertical ```tsx export const Vertical = () => ( ) ``` # Button (/docs/components/button) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Button } from "@/components/ui/Button"; import { MixerHorizontalIcon } from "@radix-ui/react-icons"; export function ButtonDemo() { return (
) } ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/button ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-slot class-variance-authority ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Button component into your `components/ui/Button.tsx` file. ```tsx /** * @description Displays a button or a component that looks like a button. */ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/utils/class-names"; const buttonVariants = cva("button", { variants: { variant: { default: "button-fill", fill: "button-fill", pill: "button-pill border border-gray-6 text-gray-11 bg-gray-2 hover:bg-gray-3", link: "button-link", menu: "button-menu", }, size: { default: "h-4", xs: "h-2.5", sm: "h-3", md: "h-4", lg: "h-5", xl: "h-6", icon: "w-3 h-3 p-0", "icon-md": "w-4 h-4 p-0", }, }, defaultVariants: { variant: "default", size: "default", }, }); interface ButtonProps extends React.ComponentProps<"button">, VariantProps { asChild?: boolean; isDisabled?: boolean; isLoading?: boolean; isLoadingText?: string | React.ReactNode; mode?: VariantProps["variant"]; label?: string | React.ReactNode; } const Button = React.forwardRef( (props: ButtonProps, ref) => { const { className, variant, mode, size, asChild = false, isDisabled, isLoading, isLoadingText, children, label, ...rest } = props; const Comp = asChild ? Slot : "button"; const disabled = isDisabled || isLoading || props.disabled; const effectiveVariant = variant ?? mode; return ( {asChild ? ( children ) : ( <> {isLoading && isLoadingText ? isLoadingText : <>{label}{children}} )} ); } ); Button.displayName = "Button"; export { Button, buttonVariants }; export type { ButtonProps }; export default Button; ``` ## Usage ### Fill ```tsx export const Fill = () => ; ``` ### Pill ```tsx export const Pill = () => ; ``` ### Link ```tsx export const Link = () => ; ``` ### Menu ```tsx export const Menu = () => ; ``` ### Sizes ```tsx export const Sizes = () => (
) ``` ### Disabled ```tsx export const Disabled = () => ; ``` ### Loading ```tsx export const Loading = () => ; ``` ### States ```tsx export const States = () => (
) ``` ### ModeProp ```tsx export const ModeProp = () => (
) ``` # Card (/docs/components/card) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction, } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { AspectRatio } from "@/components/ui/AspectRatio"; export function CardDemo() { return ( Photo by Drew Beamer Beautiful Landscape Captured in the mountains

A stunning view of nature's beauty captured at golden hour.

) ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/card ``` ### Manual Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Card component into your `components/ui/Card.tsx` file. ```tsx /** * @description Displays a card with header, content, and footer sections. */ import * as React from "react"; import { cn } from "@/utils/class-names"; function Card({ className, ...props }: React.ComponentProps<"div">) { return (
); } function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return (
); } function CardTitle({ className, ...props }: React.ComponentProps<"h3">) { return (

); } function CardDescription({ className, ...props }: React.ComponentProps<"p">) { return (

); } function CardAction({ className, ...props }: React.ComponentProps<"div">) { return (

); } function CardContent({ className, ...props }: React.ComponentProps<"div">) { return (
); } function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return (
); } export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, }; ``` ## Usage ### WithFooter ```tsx export const WithFooter = () => ( Project Setup Configure your project settings

Your project has been created successfully.

) ``` ### WithActions ```tsx export const WithActions = () => (
Notifications You have 3 unread messages

New comment on your post

2 minutes ago

) ``` ### SimpleCard ```tsx export const SimpleCard = () => (

A simple card with just content.

) ``` ### MultipleCards ```tsx export const MultipleCards = () => (
First Card This is the first card

Content for the first card.

Second Card This is the second card

Content for the second card.

Third Card This is the third card

Content for the third card.

) ``` ### NestedCards ```tsx export const NestedCards = () => ( Parent Card This card contains nested cards

Nested card 1

Nested card 2

) ``` ### InteractiveCard ```tsx export const InteractiveCard = () => ( Clickable Card Hover to see the effect

This card has hover states for interactive experiences.

) ``` ### CardGrid ```tsx export const CardGrid = () => (
{Array.from({ length: 6 }).map((_, i) => ( Card {i + 1}

Grid item content

))}
) ``` ### ImageGrid ```tsx export const ImageGrid = () => (
{[ { src: "https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80", title: "Mountain View", description: "Scenic landscape", }, { src: "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?w=800&dpr=2&q=80", title: "Workspace", description: "Modern office setup", }, { src: "https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?w=800&dpr=2&q=80", title: "Technology", description: "Digital innovation", }, ].map((item, i) => ( {item.title} {item.title} {item.description} ))}
) ``` # Carousel (/docs/components/carousel) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, } from "@/components/ui/Carousel"; import { Card, CardContent } from "@/components/ui/Card"; export function CarouselDemo() { return { return ( {Array.from({ length: 5 }).map((_, index) => (
{index + 1}
))}
); }; ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/carousel ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons ``` Copy and paste the button component into your `components/ui/Button.tsx` file. ```tsx /** * @description Displays a button or a component that looks like a button. */ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/utils/class-names"; const buttonVariants = cva("button", { variants: { variant: { default: "button-fill", fill: "button-fill", pill: "button-pill border border-gray-6 text-gray-11 bg-gray-2 hover:bg-gray-3", link: "button-link", menu: "button-menu", }, size: { default: "h-4", xs: "h-2.5", sm: "h-3", md: "h-4", lg: "h-5", xl: "h-6", icon: "w-3 h-3 p-0", "icon-md": "w-4 h-4 p-0", }, }, defaultVariants: { variant: "default", size: "default", }, }); interface ButtonProps extends React.ComponentProps<"button">, VariantProps { asChild?: boolean; isDisabled?: boolean; isLoading?: boolean; isLoadingText?: string | React.ReactNode; mode?: VariantProps["variant"]; label?: string | React.ReactNode; } const Button = React.forwardRef( (props: ButtonProps, ref) => { const { className, variant, mode, size, asChild = false, isDisabled, isLoading, isLoadingText, children, label, ...rest } = props; const Comp = asChild ? Slot : "button"; const disabled = isDisabled || isLoading || props.disabled; const effectiveVariant = variant ?? mode; return ( {asChild ? ( children ) : ( <> {isLoading && isLoadingText ? isLoadingText : <>{label}{children}} )} ); } ); Button.displayName = "Button"; export { Button, buttonVariants }; export type { ButtonProps }; export default Button; ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Carousel component into your `components/ui/Carousel.tsx` file. ```tsx "use client" import * as React from "react" import useEmblaCarousel, { type UseEmblaCarouselType, } from "embla-carousel-react" import { cn } from "@/utils/class-names" import { Button } from "@/components/ui/Button" import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons" type CarouselApi = UseEmblaCarouselType[1] type UseCarouselParameters = Parameters type CarouselOptions = UseCarouselParameters[0] type CarouselPlugin = UseCarouselParameters[1] type CarouselProps = { opts?: CarouselOptions plugins?: CarouselPlugin orientation?: "horizontal" | "vertical" setApi?: (api: CarouselApi) => void } type CarouselContextProps = { carouselRef: ReturnType[0] api: ReturnType[1] scrollPrev: () => void scrollNext: () => void canScrollPrev: boolean canScrollNext: boolean } & CarouselProps const CarouselContext = React.createContext(null) function useCarousel() { const context = React.useContext(CarouselContext) if (!context) { throw new Error("useCarousel must be used within a ") } return context } function Carousel({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }: React.ComponentProps<"div"> & CarouselProps) { const [carouselRef, api] = useEmblaCarousel( { ...opts, axis: orientation === "horizontal" ? "x" : "y", }, plugins ) const [canScrollPrev, setCanScrollPrev] = React.useState(false) const [canScrollNext, setCanScrollNext] = React.useState(false) const onSelect = React.useCallback((api: CarouselApi) => { if (!api) return setCanScrollPrev(api.canScrollPrev()) setCanScrollNext(api.canScrollNext()) }, []) const scrollPrev = React.useCallback(() => { api?.scrollPrev() }, [api]) const scrollNext = React.useCallback(() => { api?.scrollNext() }, [api]) const handleKeyDown = React.useCallback( (event: React.KeyboardEvent) => { if (event.key === "ArrowLeft") { event.preventDefault() scrollPrev() } else if (event.key === "ArrowRight") { event.preventDefault() scrollNext() } }, [scrollPrev, scrollNext] ) React.useEffect(() => { if (!api || !setApi) return setApi(api) }, [api, setApi]) React.useEffect(() => { if (!api) return onSelect(api) api.on("reInit", onSelect) api.on("select", onSelect) return () => { api?.off("select", onSelect) } }, [api, onSelect]) return (
{children}
) } function CarouselContent({ className, ...props }: React.ComponentProps<"div">) { const { carouselRef, orientation } = useCarousel() return (
) } function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { const { orientation } = useCarousel() return (
) } function CarouselPrevious({ className, variant = "pill", size = "icon", ...props }: React.ComponentProps) { const { orientation, scrollPrev, canScrollPrev } = useCarousel() return ( ) } function CarouselNext({ className, variant = "pill", size = "icon", ...props }: React.ComponentProps) { const { orientation, scrollNext, canScrollNext } = useCarousel() return ( ) } export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, useCarousel, } ``` # Checkbox (/docs/components/checkbox) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { useState } from "react"; import { Checkbox, CheckboxGroup, CheckboxGroupItem, } from "@/components/ui/Checkbox"; export function CheckboxDemo() { return { const [checked, setChecked] = useState(false); return (
setChecked(checked as boolean)} />
); }; ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/checkbox ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons radix-ui ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Checkbox component into your `components/ui/Checkbox.tsx` file. ```tsx "use client"; /** * @description A control that allows the user to toggle between checked and not checked. */ import * as React from "react"; import { Checkbox as CheckboxRadix } from "radix-ui"; import { CheckIcon } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; function Checkbox({ className, ...props }: React.ComponentProps) { return ( ); } function CheckboxIndicator({ className, ...props }: React.ComponentProps) { return ( ); } interface CheckboxGroupProps extends React.HTMLAttributes { value?: string[]; onValueChange?: (value: string[]) => void; defaultValue?: string[]; } function CheckboxGroup({ className, value, onValueChange, defaultValue, children, ...props }: CheckboxGroupProps) { const [internalValue, setInternalValue] = React.useState( defaultValue ?? [] ); const controlledValue = value ?? internalValue; const handleValueChange = React.useCallback( (itemValue: string, checked: boolean) => { const newValue = checked ? [...controlledValue, itemValue] : controlledValue.filter((v) => v !== itemValue); if (onValueChange) { onValueChange(newValue); } else { setInternalValue(newValue); } }, [controlledValue, onValueChange] ); return (
{React.Children.map(children, (child) => { if ( React.isValidElement(child) && child.type === CheckboxGroupItem ) { const childProps = child.props; return React.cloneElement(child, { ...childProps, checked: controlledValue.includes(childProps.value), onCheckedChange: (checked: boolean) => handleValueChange(childProps.value, checked), }); } return child; })}
); } interface CheckboxGroupItemProps extends Omit, "onCheckedChange"> { value: string; label?: React.ReactNode; description?: React.ReactNode; onCheckedChange?: (checked: boolean) => void; } function CheckboxGroupItem({ className, value, label, description, id, ...props }: CheckboxGroupItemProps) { const checkboxId = id || `checkbox-${value}`; return (
{(label || description) && (
{label && ( )} {description && (

{description}

)}
)}
); } export { Checkbox, CheckboxIndicator, CheckboxGroup, CheckboxGroupItem }; ```
## Usage ### Checked ```tsx export const Checked = () => (
) ``` ### Unchecked ```tsx export const Unchecked = () => (
) ``` ### Disabled ```tsx export const Disabled = () => (
) ``` ### WithDescription ```tsx export const WithDescription = () => { const [checked, setChecked] = useState(false); return (
setChecked(checked as boolean)} />

Receive emails about new products, features, and more.

); }; ``` ### Group ```tsx export const Group = () => { const [selectedItems, setSelectedItems] = useState([]); const items = [ { value: "react", label: "React", description: "A JavaScript library for building user interfaces", }, { value: "typescript", label: "TypeScript", description: "JavaScript with syntax for types", }, { value: "tailwind", label: "Tailwind CSS", description: "A utility-first CSS framework", }, { value: "nextjs", label: "Next.js", description: "The React Framework for the Web", }, ]; return (
Select your tech stack:
{items.map((item) => ( ))} {selectedItems.length > 0 && (
Selected: {selectedItems.length} item {selectedItems.length !== 1 ? "s" : ""}
)}
); }; ``` ### Indeterminate ```tsx export const Indeterminate = () => { const [selectedItems, setSelectedItems] = useState(["sub-1"]); const allItems = ["sub-1", "sub-2", "sub-3"]; const allChecked = allItems.every((item) => selectedItems.includes(item)); const someChecked = selectedItems.length > 0 && !allChecked; const handleParentChange = (checked: boolean) => { setSelectedItems(checked ? allItems : []); }; const handleChildChange = (itemId: string, checked: boolean) => { setSelectedItems((prev) => checked ? [...prev, itemId] : prev.filter((id) => id !== itemId) ); }; return (
handleParentChange(checked as boolean)} />
{allItems.map((item, index) => (
handleChildChange(item, checked as boolean) } />
))}
); }; ``` # Collapsible (/docs/components/collapsible) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { useState } from "react"; import { Collapsible, CollapsibleTrigger, CollapsibleContent, } from "@/components/ui/Collapsible"; import { Button } from "@/components/ui/Button"; import { ChevronDownIcon } from "@radix-ui/react-icons"; export function CollapsibleDemo() { return { const [isOpen, setIsOpen] = useState(false); const items = [ "React", "TypeScript", "Tailwind CSS", "Radix UI", "Vite", "Vitest", ]; return (

Technologies ({items.length})

    {items.map((item, index) => (
  • {item}
  • ))}
); }; ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/collapsible ``` ### Manual Install the following dependencies: ```bash pnpm install radix-ui ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Collapsible component into your `components/ui/Collapsible.tsx` file. ```tsx "use client"; /** * @description An interactive component which expands and collapses content. */ import { Collapsible as CollapsibleRadix } from "radix-ui"; import { cn } from "@/utils/class-names"; function Collapsible({ ...props }: React.ComponentProps) { return ; } function CollapsibleTrigger({ ...props }: React.ComponentProps) { return ( ); } function CollapsibleContent({ className, ...props }: React.ComponentProps) { return ( ); } export { Collapsible, CollapsibleTrigger, CollapsibleContent }; ``` ## Usage ### DefaultOpen ```tsx export const DefaultOpen = () => { return (

This is open by default

This collapsible starts in an open state. Click the toggle button to collapse it.
); }; ``` ### WithRichContent ```tsx export const WithRichContent = () => { const [isOpen, setIsOpen] = useState(false); return (

View Details

Product Information

This is a premium product with advanced features.

Price: $99.00
Stock: In Stock
SKU: AUR-001
Category: Components
); }; ``` ### CustomTrigger ```tsx export const CustomTrigger = () => { const [isOpen, setIsOpen] = useState(false); return (

Click anywhere to toggle

{isOpen ? "Expanded" : "Collapsed"}
The entire header area is clickable, not just the button.
); }; ``` ### Disabled ```tsx export const Disabled = () => { return (

Disabled Collapsible

This content cannot be toggled because the collapsible is disabled.
); }; ``` # Combobox (/docs/components/combobox) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { useState, useId } from "react"; import { Combobox, ComboboxInput, ComboboxTrigger, ComboboxClear, ComboboxValue, ComboboxPortal, ComboboxPositioner, ComboboxPopup, ComboboxList, ComboboxItem, ComboboxItemIndicator, ComboboxEmpty, ComboboxChips, ComboboxChip, ComboboxChipRemove, ComboboxGroup, ComboboxGroupLabel, ComboboxSeparator, } from "@/components/ui/Combobox"; import { Button } from "@/components/ui/Button"; import { Label } from "@/components/ui/Label"; import { CheckIcon, Cross2Icon, ChevronDownIcon } from "@radix-ui/react-icons"; export function ComboboxDemo() { return { const id = useId(); return (
No fruits found. {(item: string) => ( {item} )}
); }; ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/combobox ``` ### Manual Install the following dependencies: ```bash pnpm install @base-ui/react ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Combobox component into your `components/ui/Combobox.tsx` file. ```tsx "use client"; /** * @description A combobox component that allows users to search and select from a list of options. */ import * as React from "react"; import { Combobox } from "@base-ui/react/combobox"; import { cn } from "@/utils/class-names"; const ComboboxRoot = Combobox.Root; function ComboboxInput({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxTrigger({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxIcon({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxClear({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxValue({ ...props }: React.ComponentProps) { return ; } function ComboboxChips({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxChip({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxChipRemove({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxPortal({ ...props }: React.ComponentProps) { return ; } function ComboboxBackdrop({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxPositioner({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxPopup({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxArrow({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxStatus({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxEmpty({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxList({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxRow({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxItem({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxItemIndicator({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxSeparator({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxGroup({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxGroupLabel({ className, ...props }: React.ComponentProps) { return ( ); } function ComboboxCollection({ ...props }: React.ComponentProps) { return ( ); } export { ComboboxRoot as Combobox, ComboboxInput, ComboboxTrigger, ComboboxIcon, ComboboxClear, ComboboxValue, ComboboxChips, ComboboxChip, ComboboxChipRemove, ComboboxPortal, ComboboxBackdrop, ComboboxPositioner, ComboboxPopup, ComboboxArrow, ComboboxStatus, ComboboxEmpty, ComboboxList, ComboboxRow, ComboboxItem, ComboboxItemIndicator, ComboboxSeparator, ComboboxGroup, ComboboxGroupLabel, ComboboxCollection, }; ``` ## Usage ### WithClearButton ```tsx export const WithClearButton = () => { const id = `combobox-clear-${Math.random().toString(36).substr(2, 9)}`; return (
No fruits found. {(item: string) => ( {item} )}
); }; ``` ### WithTriggerButton ```tsx export const WithTriggerButton = () => { const id = useId(); return (
No fruits found. {(item: string) => ( {item} )}
); }; ``` ### WithChips ```tsx export const WithChips = () => { const id = useId(); return (
No fruits found. {(item: string) => ( {item} )}
); }; ``` ### WithGroups ```tsx export const WithGroups = () => { const id = useId(); const regions = Array.from(new Set(countries.map((c) => c.region))); return (
item.value}>
No countries found. {regions.map((region) => ( {region} {countries .filter((c) => c.region === region) .map((country) => ( {country.label} ))} ))}
); }; ``` ### WithSeparator ```tsx export const WithSeparator = () => { const id = useId(); const popularFruits = ["Apple", "Banana", "Orange", "Grape"]; const otherFruits = fruits.filter((f) => !popularFruits.includes(f)); return (
No fruits found. Popular {popularFruits.map((item) => ( {item} ))} Others {otherFruits.map((item) => ( {item} ))}
); }; ``` ### Controlled ```tsx export const Controlled = () => { const [value, setValue] = useState(null); const id = useId(); return (
Selected: {value || "None"}
No fruits found. {(item: string) => ( {item} )}
); }; ``` ### Disabled ```tsx export const Disabled = () => { const id = useId(); return (
No fruits found. {(item: string) => ( {item} )}
); }; ``` ### Positioning ```tsx export const Positioning = () => { const id = useId(); return (
{(["top", "right", "bottom", "left"] as const).map((side) => (
No fruits found. {(item: string) => ( {item} )}
))}
); }; ``` # Command (/docs/components/command) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import * as React from "react"; import { ArrowUpIcon, ArrowDownIcon, ArrowTopRightIcon, } from "@radix-ui/react-icons"; import { Button } from "@/components/ui/Button"; import { Command, CommandCollection, CommandDialog, CommandDialogPopup, CommandDialogTrigger, CommandEmpty, CommandFooter, CommandGroup, CommandGroupLabel, CommandInput, CommandItem, CommandList, CommandPanel, CommandSeparator, CommandShortcut, } from "@/components/ui/Command"; import { Kbd, KbdGroup } from "@/components/ui/Kbd"; export function CommandDemo() { return { const [open, setOpen] = React.useState(false); function handleItemClick(_item: Item) { setOpen(false); } React.useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "j" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((open) => !open); console.log("down"); } }; document.addEventListener("keydown", down); return () => document.removeEventListener("keydown", down); }, []); return ( }> Open Command Palette ⌘J No results found. {(group: Group, _index: number) => ( {group.value} {(item: Item) => ( handleItemClick(item)} value={item.value} > {item.label} {item.shortcut && ( {item.shortcut} )} )} )}
Navigate
Open
K Close
); }; ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/command ``` ### Manual Install the following dependencies: ```bash pnpm install @base-ui/react @radix-ui/react-icons ``` Copy and paste the autocomplete component into your `components/ui/Autocomplete.tsx` file. ```tsx "use client"; /** * @description Displays an autocomplete input with a list of options. */ import * as React from "react"; import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete"; import { Cross1Icon, CaretSortIcon } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; import { Input } from "@/components/ui/Input"; import { ScrollArea } from "@/components/ui/ScrollArea"; function Autocomplete({ ...props }: React.ComponentProps) { return ; } function AutocompleteInput({ className, showTrigger = false, showClear = false, startAddon, size, ...props }: Omit & { showTrigger?: boolean; showClear?: boolean; startAddon?: React.ReactNode; size?: "sm" | "default" | "lg" | number; ref?: React.Ref; }) { const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number; return (
{startAddon && ( )} } {...props} /> {showTrigger && ( )} {showClear && ( )}
); } function AutocompletePopup({ className, children, sideOffset = 4, ...props }: AutocompletePrimitive.Popup.Props & { sideOffset?: number; }) { return ( {children} ); } function AutocompleteItem({ className, children, ...props }: AutocompletePrimitive.Item.Props) { return ( {children} ); } function AutocompleteSeparator({ className, ...props }: AutocompletePrimitive.Separator.Props) { return ( ); } function AutocompleteGroup({ ...props }: AutocompletePrimitive.Group.Props) { return ; } function AutocompleteGroupLabel({ className, ...props }: AutocompletePrimitive.GroupLabel.Props) { return ( ); } function AutocompleteEmpty({ className, ...props }: AutocompletePrimitive.Empty.Props) { return ( ); } function AutocompleteRow({ className, ...props }: AutocompletePrimitive.Row.Props) { return ( ); } function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) { return ( ); } function AutocompleteList({ className, ...props }: AutocompletePrimitive.List.Props) { return ( ); } function AutocompleteClear({ className, ...props }: AutocompletePrimitive.Clear.Props) { return ( ); } function AutocompleteStatus({ className, ...props }: AutocompletePrimitive.Status.Props) { return ( ); } function AutocompleteCollection({ ...props }: AutocompletePrimitive.Collection.Props) { return ( ); } function AutocompleteTrigger({ className, ...props }: AutocompletePrimitive.Trigger.Props) { return ( ); } export { Autocomplete, AutocompleteInput, AutocompleteTrigger, AutocompletePopup, AutocompleteItem, AutocompleteSeparator, AutocompleteGroup, AutocompleteGroupLabel, AutocompleteEmpty, AutocompleteValue, AutocompleteList, AutocompleteClear, AutocompleteStatus, AutocompleteRow, AutocompleteCollection, }; ```
Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Command component into your `components/ui/Command.tsx` file. ```tsx "use client"; import { Dialog as CommandDialogPrimitive } from "@base-ui/react/dialog"; import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; import * as React from "react"; import { cn } from "@/utils/class-names"; import { Autocomplete, AutocompleteCollection, AutocompleteEmpty, AutocompleteGroup, AutocompleteGroupLabel, AutocompleteInput, AutocompleteItem, AutocompleteList, AutocompleteSeparator, } from "@/components/ui/Autocomplete"; const CommandInputContext = React.createContext<{ inputRef: React.RefObject | null; }>({ inputRef: null, }); const CommandDialog = CommandDialogPrimitive.Root; const CommandDialogPortal = CommandDialogPrimitive.Portal; function CommandDialogTrigger(props: CommandDialogPrimitive.Trigger.Props) { return ( ); } function CommandDialogBackdrop({ className, ...props }: CommandDialogPrimitive.Backdrop.Props) { return ( ); } function CommandDialogViewport({ className, ...props }: CommandDialogPrimitive.Viewport.Props) { return ( ); } function CommandDialogPopup({ className, children, ...props }: CommandDialogPrimitive.Popup.Props) { const inputRef = React.useRef(null); return ( {children} ); } function Command({ autoHighlight = "always", keepHighlight = true, open = true, ...props }: React.ComponentProps) { return ( ); } function CommandInput({ className, placeholder = undefined, ...props }: React.ComponentProps) { const { inputRef } = React.useContext(CommandInputContext); return (
} {...props} />
); } function CommandList({ className, ...props }: React.ComponentProps) { return ( ); } function CommandEmpty({ className, ...props }: React.ComponentProps) { return ( ); } function CommandPanel({ className, ...props }: React.ComponentProps<"div">) { return (
); } function CommandGroup({ className, ...props }: React.ComponentProps) { return ( ); } function CommandGroupLabel({ className, ...props }: React.ComponentProps) { return ( ); } function CommandCollection({ ...props }: React.ComponentProps) { return ; } function CommandItem({ className, ...props }: React.ComponentProps) { return ( ); } function CommandSeparator({ className, ...props }: React.ComponentProps) { return ( ); } function CommandShortcut({ className, ...props }: React.ComponentProps<"kbd">) { return ( ); } function CommandFooter({ className, ...props }: React.ComponentProps<"div">) { return (
); } export { Command, CommandCollection, CommandDialog, CommandDialogPopup, CommandDialogTrigger, CommandEmpty, CommandFooter, CommandGroup, CommandGroupLabel, CommandInput, CommandItem, CommandList, CommandPanel, CommandSeparator, CommandShortcut, }; ``` # Context Menu (/docs/components/context-menu) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { useState } from "react"; import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup, } from "@/components/ui/ContextMenu"; export function ContextMenuDemo() { return ( Right click here Back ⌘[ Forward ⌘] Reload ⌘R Save As... ⇧⌘S Print... ) } ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/context-menu ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons radix-ui ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the ContextMenu component into your `components/ui/ContextMenu.tsx` file. ```tsx "use client"; /** * @description Displays a menu to the user triggered by right-click or long-press. */ import * as React from "react"; import { ContextMenu as ContextMenuRadix } from "radix-ui"; import { CheckIcon, ChevronRightIcon, DotFilledIcon, } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; function ContextMenu({ ...props }: React.ComponentProps) { return ; } function ContextMenuTrigger({ className, ...props }: React.ComponentProps) { return ( ); } function ContextMenuGroup({ ...props }: React.ComponentProps) { return ; } function ContextMenuPortal({ ...props }: React.ComponentProps) { return ; } function ContextMenuSub({ ...props }: React.ComponentProps) { return ; } function ContextMenuRadioGroup({ className, ...props }: React.ComponentProps) { return ( ); } function ContextMenuSubTrigger({ children, className, ...props }: React.ComponentProps) { return ( {children}
); } function ContextMenuSubContent({ className, ...props }: React.ComponentProps) { return ( ); } function ContextMenuContent({ className, ...props }: React.ComponentProps) { return ( ); } function ContextMenuItem({ className, ...props }: React.ComponentProps & {}) { return ( ); } function ContextMenuCheckboxItem({ children, checked, className, ...props }: React.ComponentProps) { return ( {children} ); } function ContextMenuRadioItem({ children, className, ...props }: React.ComponentProps) { return ( {children} ); } function ContextMenuLabel({ className, ...props }: React.ComponentProps) { return ( ); } function ContextMenuSeparator({ className, ...props }: React.ComponentProps) { return ( ); } function ContextMenuShortcut({ ...props }: React.ComponentProps<"span">) { return ; } export { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup, }; ```
## Usage ### WithSubmenu ```tsx export const WithSubmenu = () => ( Right click here Back Forward Reload More Tools Save Page As... ⇧⌘S Create Shortcut... Name Window... Developer Tools Inspect ⌥⌘I ) ``` ### WithCheckboxes ```tsx export const WithCheckboxes = () => { const [showBookmarksBar, setShowBookmarksBar] = useState(true); const [showFullUrls, setShowFullUrls] = useState(false); return ( Right click here Show Bookmarks Bar ⌘⇧B Show Full URLs ); }; ``` ### WithRadioGroup ```tsx export const WithRadioGroup = () => { const [person, setPerson] = useState("pedro"); return ( Right click here People Pedro Duarte Colm Tuite ); }; ``` ### ComplexMenu ```tsx export const ComplexMenu = () => { const [showBookmarksBar, setShowBookmarksBar] = useState(true); const [showFullUrls, setShowFullUrls] = useState(false); const [person, setPerson] = useState("pedro"); return ( Right click here Back ⌘[ Forward ⌘] Reload ⌘R Show Bookmarks Bar ⌘⇧B Show Full URLs People Pedro Duarte Colm Tuite ); }; ``` # Dialog (/docs/components/dialog) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Button } from "@/components/ui/Button"; import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/Dialog"; import { Input } from "@/components/ui/Input"; export function DialogDemo() { return ( Edit profile Make changes to your profile here. Click save when you're done.
) } ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/dialog ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons radix-ui ``` Copy and paste the button component into your `components/ui/Button.tsx` file. ```tsx /** * @description Displays a button or a component that looks like a button. */ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/utils/class-names"; const buttonVariants = cva("button", { variants: { variant: { default: "button-fill", fill: "button-fill", pill: "button-pill border border-gray-6 text-gray-11 bg-gray-2 hover:bg-gray-3", link: "button-link", menu: "button-menu", }, size: { default: "h-4", xs: "h-2.5", sm: "h-3", md: "h-4", lg: "h-5", xl: "h-6", icon: "w-3 h-3 p-0", "icon-md": "w-4 h-4 p-0", }, }, defaultVariants: { variant: "default", size: "default", }, }); interface ButtonProps extends React.ComponentProps<"button">, VariantProps { asChild?: boolean; isDisabled?: boolean; isLoading?: boolean; isLoadingText?: string | React.ReactNode; mode?: VariantProps["variant"]; label?: string | React.ReactNode; } const Button = React.forwardRef( (props: ButtonProps, ref) => { const { className, variant, mode, size, asChild = false, isDisabled, isLoading, isLoadingText, children, label, ...rest } = props; const Comp = asChild ? Slot : "button"; const disabled = isDisabled || isLoading || props.disabled; const effectiveVariant = variant ?? mode; return ( {asChild ? ( children ) : ( <> {isLoading && isLoadingText ? isLoadingText : <>{label}{children}} )} ); } ); Button.displayName = "Button"; export { Button, buttonVariants }; export type { ButtonProps }; export default Button; ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Dialog component into your `components/ui/Dialog.tsx` file. ```tsx /** * @description A window overlaid on the primary content, rendering content in a layer above the page. */ import { Dialog as DialogPrimitive } from "radix-ui"; import { Cross2Icon } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; import { Button } from "@/components/ui/Button"; function Dialog({ ...props }: React.ComponentProps) { return ; } function DialogTrigger({ ...props }: React.ComponentProps) { return ; } function DialogPortal({ ...props }: React.ComponentProps) { return ; } function DialogClose({ ...props }: React.ComponentProps) { return ; } function DialogOverlay({ className, ...props }: React.ComponentProps) { return ( ); } function DialogContent({ className, children, ...props }: React.ComponentProps) { return ( {children} ); } function DialogHeader({ ...props }: React.ComponentProps<"div">) { return
; } function DialogFooter({ ...props }: React.ComponentProps<"div">) { return (
); } function DialogTitle({ ...props }: React.ComponentProps) { return ; } function DialogDescription({ ...props }: React.ComponentProps) { return ( ); } export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, }; ``` ## Usage ### CustomContent ```tsx export const CustomContent = () => ( Are you sure absolutely sure? This action cannot be undone. This will permanently delete your account and remove your data from our servers. ) ``` # Drawer (/docs/components/drawer) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { Button } from "@/components/ui/Button"; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from "@/components/ui/Drawer"; export function DrawerDemo() { return (
Move Goal Set your daily activity goal.
350
Calories/day
) } ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/drawer ``` ### Manual Install the following dependencies: ```bash pnpm install vaul ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Drawer component into your `components/ui/Drawer.tsx` file. ```tsx "use client"; /** * @description A drawer component that slides in from the edge of the screen. */ import * as React from "react"; import { Drawer as DrawerPrimitive } from "vaul"; import { cn } from "@/utils/class-names"; function Drawer({ ...props }: React.ComponentProps) { return ; } function DrawerTrigger({ ...props }: React.ComponentProps) { return ; } function DrawerPortal({ ...props }: React.ComponentProps) { return ; } function DrawerClose({ ...props }: React.ComponentProps) { return ; } function DrawerOverlay({ className, ...props }: React.ComponentProps) { return ( ); } function DrawerContent({ className, children, ...props }: React.ComponentProps) { return (
{children} ); } function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { return (
); } function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { return (
); } function DrawerTitle({ className, ...props }: React.ComponentProps) { return ( ); } function DrawerDescription({ className, ...props }: React.ComponentProps) { return ( ); } export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription, }; ``` # Dropdown Menu (/docs/components/dropdown-menu) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { useState } from "react"; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, } from "@/components/ui/DropdownMenu"; import { Button } from "@/components/ui/Button"; export function DropdownMenuDemo() { return ( My Account Profile ⇧⌘P Billing ⌘B Settings ⌘S Keyboard shortcuts ⌘K Team Invite users Email Message More... New Team Log out ⇧⌘Q ) } ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/dropdown-menu ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons radix-ui ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the DropdownMenu component into your `components/ui/DropdownMenu.tsx` file. ```tsx "use client"; /** * @description Displays a menu of actions or options triggered by a button. */ import * as React from "react"; import { DropdownMenu as DropdownMenuRadix } from "radix-ui"; import { CheckIcon, ChevronRightIcon, CircleIcon, DotFilledIcon, } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; function DropdownMenu({ ...props }: React.ComponentProps) { return ; } function DropdownMenuPortal({ ...props }: React.ComponentProps) { return ( ); } function DropdownMenuTrigger({ className, ...props }: React.ComponentProps) { return ( ); } function DropdownMenuContent({ className, ...props }: React.ComponentProps) { return ( ); } function DropdownMenuGroup({ ...props }: React.ComponentProps) { return ; } function DropdownMenuItem({ className, ...props }: React.ComponentProps) { return ( ); } function DropdownMenuCheckboxItem({ children, className, ...props }: React.ComponentProps) { return ( {children} ); } function DropdownMenuRadioGroup({ className, ...props }: React.ComponentProps) { return ( ); } function DropdownMenuRadioItem({ children, className, ...props }: React.ComponentProps) { return ( {children} ); } function DropdownMenuLabel({ className, ...props }: React.ComponentProps) { return ( ); } function DropdownMenuSeparator({ className, ...props }: React.ComponentProps) { return ( ); } function DropdownMenuShortcut({ ...props }: React.ComponentProps<"span">) { return ; } function DropdownMenuSub({ ...props }: React.ComponentProps) { return ; } function DropdownMenuSubTrigger({ children, className, ...props }: React.ComponentProps) { return ( {children}
); } function DropdownMenuSubContent({ className, ...props }: React.ComponentProps) { return ( ); } export { DropdownMenu, DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, }; ```
## Usage ### WithCheckboxes ```tsx export const WithCheckboxes = () => { const [showStatusBar, setShowStatusBar] = useState(true); const [showActivityBar, setShowActivityBar] = useState(false); const [showPanel, setShowPanel] = useState(false); return ( Appearance Status Bar Activity Bar Panel ); }; ``` ### WithRadioGroup ```tsx export const WithRadioGroup = () => { const [position, setPosition] = useState("bottom"); return ( Panel Position Top Bottom Right ); }; ``` ### Complex ```tsx export const Complex = () => { const [showStatusBar, setShowStatusBar] = useState(true); const [showPanel, setShowPanel] = useState(false); const [position, setPosition] = useState("bottom"); return ( My Account Profile ⇧⌘P Billing ⌘B Settings ⌘S Appearance Status Bar Panel Panel Position Top Bottom Right Log out ⇧⌘Q ); }; ``` # Empty (/docs/components/empty) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { EyeClosedIcon } from "@radix-ui/react-icons"; import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia, } from "@/components/ui/Empty"; export function EmptyDemo() { return { return ( No items yet Get started by adding your first item. You can always change this later. ); }; ``` ## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/empty ``` ### Manual Install the following dependencies: ```bash pnpm install class-variance-authority ``` Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the Empty component into your `components/ui/Empty.tsx` file. ```tsx import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/utils/class-names" function Empty({ className, ...props }: React.ComponentProps<"div">) { return (
) } function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { return (
) } const emptyMediaVariants = cva( "mb-0.5 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", { variants: { variant: { default: "bg-transparent", icon: "bg-muted text-foreground flex size-5 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6", }, }, defaultVariants: { variant: "default", }, } ) function EmptyMedia({ className, variant = "default", ...props }: React.ComponentProps<"div"> & VariantProps) { return (
) } function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { return (
) } function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { return (
a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", className )} {...props} /> ) } function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { return (
) } export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia, } ``` # Form (/docs/components/form) import { ComponentPreview } from "@/components/ComponentPreview" import { ComponentSource } from "@/components/ComponentSource" import { Steps, Step } from "fumadocs-ui/components/steps" ## Preview ```tsx import { useRef } from "react"; import * as React from "react"; import { Form, FormField, FormSubmit, FormSwitch, FormCheckbox, FormCheckboxGroup, FormAlert, } from "@/components/ui/Form"; import { Input } from "@/components/ui/Input"; import { FormFieldCombobox } from "@/components/FormFieldCombobox"; import { FormFieldSelect } from "@/components/FormFieldSelect"; import { FormFieldSignaturePad } from "@/components/FormFieldSignaturePad"; import { FormFieldSortableList } from "@/components/FormFieldSortableList"; import { FormFieldEditor } from "@/components/FormFieldEditor"; import { useFormDynamic } from "@/hooks/use-dynamic-form"; import { validateFormData } from "@/utils/web-validation"; export function FormDemo() { return { const formRef = useRef(null); const formData = useFormDynamic({ name: "text", email: "text", }); const { name, email } = formData.getFields(); const { isValid, errors } = validateFormData( defaultSchema, formData.getValues() ); const formErrors = errors || undefined; const handleOnSubmit = async (event: React.FormEvent) => { event.preventDefault(); formData.setFetchStatus("loading"); if (!isValid) { formData.setFetchStatus("error"); formData.touchForm(); formData.setError("Please complete all required fields"); return; } // Simulate API call await new Promise((resolve) => setTimeout(resolve, 1000)); formData.setFetchStatus("success"); console.log("Form submitted:", formData.getValues()); }; const formDataForAlert = { fetchStatus: formData.fetchStatus, error: formData.error, }; return (
); }; ```
## Installation Make sure that `namespace` is set in your component.json file. Namespace docs: [Learn more about namespaces](/docs/namespace) ```bash pnpm dlx shadcn@latest add @aura/form ``` ### Manual Install the following dependencies: ```bash pnpm install @radix-ui/react-icons ajv radix-ui ``` Copy and paste the alert status component into your `components/AlertStatus.tsx` file. ```tsx import React from "react"; import { InfoCircledIcon, CheckCircledIcon, ExclamationTriangleIcon, CrossCircledIcon, QuoteIcon, } from "@radix-ui/react-icons"; import { Alert, AlertContent, AlertTitle, AlertDescription, AlertIcon, } from "@/components/ui/Alert"; export type AlertProps = { status?: "info" | "success" | "warning" | "danger" | "other"; title?: React.ReactNode; description?: React.ReactNode; icon?: React.ReactNode; showIcon?: boolean; }; const AlertStatus = ({ status = "other", title, description, icon, showIcon = true, ...props }: AlertProps) => { // Map status to Alert variant const variantMap: Record< "info" | "success" | "warning" | "danger" | "other", "info" | "success" | "warning" | "danger" | "default" > = { info: "info", success: "success", warning: "warning", danger: "danger", other: "default", }; // Map status to default icons const iconMap = { info: InfoCircledIcon, success: CheckCircledIcon, warning: ExclamationTriangleIcon, danger: CrossCircledIcon, other: QuoteIcon, }; const variant = variantMap[status]; const DefaultIcon = iconMap[status]; return ( {showIcon && ( {icon ? icon : } )} {title && {title}} {description && {description}} ); }; export default AlertStatus; ``` Copy and paste the checkbox component into your `components/ui/Checkbox.tsx` file. ```tsx "use client"; /** * @description A control that allows the user to toggle between checked and not checked. */ import * as React from "react"; import { Checkbox as CheckboxRadix } from "radix-ui"; import { CheckIcon } from "@radix-ui/react-icons"; import { cn } from "@/utils/class-names"; function Checkbox({ className, ...props }: React.ComponentProps) { return ( ); } function CheckboxIndicator({ className, ...props }: React.ComponentProps) { return ( ); } interface CheckboxGroupProps extends React.HTMLAttributes { value?: string[]; onValueChange?: (value: string[]) => void; defaultValue?: string[]; } function CheckboxGroup({ className, value, onValueChange, defaultValue, children, ...props }: CheckboxGroupProps) { const [internalValue, setInternalValue] = React.useState( defaultValue ?? [] ); const controlledValue = value ?? internalValue; const handleValueChange = React.useCallback( (itemValue: string, checked: boolean) => { const newValue = checked ? [...controlledValue, itemValue] : controlledValue.filter((v) => v !== itemValue); if (onValueChange) { onValueChange(newValue); } else { setInternalValue(newValue); } }, [controlledValue, onValueChange] ); return (
{React.Children.map(children, (child) => { if ( React.isValidElement(child) && child.type === CheckboxGroupItem ) { const childProps = child.props; return React.cloneElement(child, { ...childProps, checked: controlledValue.includes(childProps.value), onCheckedChange: (checked: boolean) => handleValueChange(childProps.value, checked), }); } return child; })}
); } interface CheckboxGroupItemProps extends Omit, "onCheckedChange"> { value: string; label?: React.ReactNode; description?: React.ReactNode; onCheckedChange?: (checked: boolean) => void; } function CheckboxGroupItem({ className, value, label, description, id, ...props }: CheckboxGroupItemProps) { const checkboxId = id || `checkbox-${value}`; return (
{(label || description) && (
{label && ( )} {description && (

{description}

)}
)}
); } export { Checkbox, CheckboxIndicator, CheckboxGroup, CheckboxGroupItem }; ```
Copy and paste the class names utility into your `utils/class-names.ts` file. ```ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` Copy and paste the radio group component into your `components/ui/RadioGroup.tsx` file. ```tsx "use client"; import * as React from "react"; import { RadioGroup as RadioGroupPrimitive } from "radix-ui"; import { cn } from "@/utils/class-names"; function RadioGroup({ className, ...props }: React.ComponentProps) { return ( ); } function RadioGroupItem({ className, ...props }: React.ComponentProps) { return ( ); } export { RadioGroup, RadioGroupItem }; ``` Copy and paste the switch component into your `components/ui/Switch.tsx` file. ```tsx "use client"; /** * @description A control that allows the user to toggle between on and off states. */ import * as React from "react"; import { Switch as SwitchPrimitive } from "radix-ui"; import { cn } from "@/utils/class-names"; function Switch({ className, ...props }: React.ComponentProps) { return ( ); } function SwitchThumb({ className, ...props }: React.ComponentProps) { return ( ); } export { Switch, SwitchThumb }; ``` Copy and paste the use dynamic form hook into your `hooks/use-dynamic-form.ts` file. ```ts import { useState } from "react"; // Define the possible field types for the form. type FieldType = "text" | "textarea" | "select" | "checkbox"; // Interface to define the structure of the initial values object. // It maps field names (string) to their respective field types. interface useFormDynamicProps { [key: string]: FieldType; } // Object to resolve the initial value based on the field type. const initialValueResolver = { text: "", textarea: "", select: "", checkbox: false, }; /** * Custom hook to manage the state of input fields in a form. * @param initialValues - An object where keys are field names and values are field types. * @returns An object containing the current values, errors, touch states, and functions to manage them. */ const useInputValueFields = (initialValues: useFormDynamicProps = {}) => { // Resolve initial values based on the provided types. const resolvedInitialValues = Object.entries(initialValues).reduce( (acc, [key, type]) => { acc[key] = initialValueResolver[type]; return acc; }, {} as Record ); // State to hold the current values of the form fields. const [value, setValue] = useState>( resolvedInitialValues ); // State to hold any errors related to the form fields. const [error, setError] = useState(null); // State to track if a field has been touched (focused and blurred). const [touch, setTouch] = useState>({}); return { types: initialValues, // The types of the fields. value, // The current values of the fields. error, // Any errors associated with the fields. touch, // The touch state of the fields. setTouch, // Function to update the touch state. setValue, // Function to update the field values. setError, // Function to update the error state. }; }; // Type definition for the props of a single field. export type FieldProps = { name: string; // The name of the field. type: FieldType; // The type of the field. value: string | boolean; // The current value of the field. setValue: (value: string | boolean) => void; // Function to set the value of the field. onChange: ( event: React.ChangeEvent< HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement > ) => void; // Function to handle changes in the field. onCheckedChange: React.ChangeEventHandler & ((checked: boolean) => void); // Function to handle changes in checkbox fields. touch: boolean; // Whether the field has been touched. setTouch: (value: boolean) => void; // Function to set the touch state of the field. reset: () => void; // Function to reset the field to its default value. }; /** * Custom hook to manage a dynamic form with multiple fields. * @param initialValues - An object defining the initial field types. * @returns An object containing form state, field management functions, and form-level actions. */ export const useFormDynamic = ( initialValues: useFormDynamicProps, formGeneralRef?: React.RefObject ) => { // State to track the status of a fetch operation (e.g., submitting the form). const [fetchStatus, setFetchStatus] = useState< "idle" | "loading" | "success" | "error" >("idle"); // Use the useInputValueFields hook to manage the fields. const fields = useInputValueFields(initialValues); /** * Updates a specific field's value and touch state. * @param name - The name of the field to update. * @param updates - An object containing the new value and/or touch state. */ const updateField = ( name: string, updates: { value?: string | boolean; touch?: boolean } ) => { // Update the field's value. fields.setValue((prev) => { const newValues = { ...prev, [name]: updates.value }; return newValues; }); // Update the field's touch state. fields.setTouch((prev) => ({ ...prev, [name]: updates.touch })); }; /** * Returns the props for a specific field. * @param name - The name of the field. * @returns An object containing the field's props. */ const field = (name: string): FieldProps => { const fieldType = fields.types[name]; const defaultValue = initialValueResolver[fieldType]; // Handles changes in the field's value. const handleChange = ( event: React.ChangeEvent< HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement > ) => { const value = event.target.value; updateField(name, { value, touch: true, }); }; // Handles changes in checkbox fields. const handleOnCheckedChange = (event: boolean): void => { updateField(name, { value: event, touch: true, }); }; // Sets the field's value in the DOM and updates the state. const setFormFieldValue = ( value: string | boolean, formRef = formGeneralRef ): void => { if (formRef) { const input = formRef?.current?.querySelector(`[name="${name}"]`) as | HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | null; if (input) { if (input instanceof HTMLInputElement && input.type === "checkbox") { (input as HTMLInputElement).checked = value as boolean; } else { input.value = String(value); } } } updateField(name, { value: value, }); }; return { name, type: fields.types[name], value: fields.value[name] ?? defaultValue, setValue: setFormFieldValue, onChange: handleChange, onCheckedChange: handleOnCheckedChange as React.ChangeEventHandler & ((checked: boolean) => void), touch: fields.touch[name] ?? false, setTouch: (value: boolean) => updateField(name, { touch: value }), reset: () => updateField(name, { value: defaultValue, touch: false, }), }; }; /** * Returns an object containing the props for all fields. * @returns An object where keys are field names and values are their props. */ const getFields = () => { return Object.keys(fields.value).reduce( (acc, key) => { acc[key] = field(key); return acc; }, {} as Record> ); }; /** * Resets the form to its initial state. * @param formRef - A ref to the form element. * @param initialValues - The initial values for the form fields. */ const resetForm = ( formRef: React.RefObject, initialValues?: Record ): void => { const fields = getFields(); for (const field in fields) { fields[field].reset(); fields[field].setFormFieldValue( formRef, initialValues?.[field] ?? initialValueResolver[fields[field].type] ); } }; /** * Touches all fields in the form. */ const touchForm = () => { const fields = getFields(); for (const field in fields) { updateField(field, { value: fields[field].value, touch: true }); } }; /** * Returns an object containing the current values of all fields. * @returns An object where keys are field names and values are their current values. */ const getValues = () => { return Object.keys(fields.value).reduce( (acc, key) => { acc[key] = fields.value[key]; return acc; }, {} as Record ); }; return { ...fields, // Spread the fields object to include its properties. resetForm, // Function to reset the form. touchForm, // Function to touch all fields. field, // Function to get the props for a specific field. getFields, // Function to get the props for all fields. getValues, // Function to get the current values of all fields. fetchStatus, // The current fetch status. setFetchStatus, // Function to set the fetch status. }; }; ``` Copy and paste the Form component into your `components/ui/Form.tsx` file. ```tsx import * as React from "react"; import { ErrorObject } from "ajv"; import { Form as FormRadix } from "radix-ui"; import { ChevronDownIcon, SymbolIcon } from "@radix-ui/react-icons"; import { FieldProps } from "@/hooks/use-dynamic-form"; import AlertStatus from "@/components/AlertStatus"; import Button, { ButtonProps } from "@/components/ui/Button"; import { cn } from "@/utils/class-names"; import { Checkbox, CheckboxGroup, CheckboxGroupItem, } from "@/components/ui/Checkbox"; import { Switch } from "@/components/ui/Switch"; import { RadioGroup, RadioGroupItem } from "@/components/ui/RadioGroup"; interface FormProps extends FormRadix.FormProps { errors?: ErrorObject, unknown>[]; } export const Form = React.forwardRef( ({ children, errors, ...props }, forwardedRef) => { const childrenArray = React.Children.toArray(children); const processChildren = ( children: React.ReactNode[] ): React.ReactNode[] => { return children.map((child) => { if (!React.isValidElement(child)) { return child; } if (child.props?.field) { const fieldErrors = errors?.filter( (error) => error.instancePath.slice(1) === child.props?.field?.name ); return React.cloneElement(child as React.ReactElement, { ...child.props, errors: fieldErrors, }); } if (child.props?.children) { const processedChildren = processChildren( React.Children.toArray(child.props.children) ); return React.cloneElement(child as React.ReactElement, { ...child.props, children: processedChildren, }); } return child; }); }; return ( {processChildren(childrenArray)} ); } ); interface FormSubmitProps extends FormRadix.FormSubmitProps { buttonProps?: ButtonProps; fetchStatus?: "idle" | "loading" | "success" | "error"; } export const FormSubmit = React.forwardRef( ({ buttonProps, fetchStatus, ...props }, forwardedRef) => { return (