# 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 (
)
```
## 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 = () => (
)
```
# 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 (
);
}
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 (
);
}
);
interface FormFieldProps extends Partial {
label: React.ReactNode;
labelProps?: FormRadix.FormLabelProps;
controlProps?: FormRadix.FormControlProps;
field?: FieldProps;
errors?: ErrorObject, unknown>[];
}
export const FormField = React.forwardRef(
(
{ labelProps, label, controlProps, children, field, errors, ...props },
forwardedRef
) => {
const classNameConnect: string[] = ["flex flex-col gap-0.5"];
const hasError = field.touch && errors && errors.length > 0;
const hasSelect = React.Children.toArray(children).some(
(child: any) => child?.type === "select"
);
if (props.className) {
classNameConnect.push(props.className);
}
const name = props.name || field?.name;
return (
{label && {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/grid
```
### 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 Grid component into your `components/ui/Grid.tsx` file.
```tsx
/**
* @description Displays a grid container with configurable column layout.
*/
import * as React from "react";
import { cn } from "@/utils/class-names";
type AuraGrid = "one" | "two" | "three" | "four" | "five" | "six" | "seven" | "eight" | "nine" | "ten" | "eleven" | "twelve";
interface GridProps extends React.ComponentProps<"div"> {
col: AuraGrid;
isFixed?: boolean;
}
const Grid = React.forwardRef(
({ col, isFixed, className, ...props }, ref) => {
return (
);
}
);
Grid.displayName = "Grid";
export { Grid };
export type { GridProps };
export default Grid;
```
# Hover Card (/docs/components/hover-card)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
} from "@/components/ui/HoverCard";
import { Button } from "@/components/ui/Button";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/Avatar";
import { CalendarIcon } from "@radix-ui/react-icons";
export function HoverCardDemo() {
return (
CN
shadcn
Designed and built with all the love in the world by @shadcn.
{" "}
Joined December 2021
)
```
## 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/hover-card
```
### 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 HoverCard component into your `components/ui/HoverCard.tsx` file.
```tsx
"use client";
/**
* @description For sighted users to preview content available behind a link.
*/
import * as React from "react";
import { HoverCard as HoverCardRadix } from "radix-ui";
import { cn } from "@/utils/class-names";
function HoverCard({
...props
}: React.ComponentProps) {
return ;
}
function HoverCardTrigger({
className,
...props
}: React.ComponentProps) {
return (
);
}
function HoverCardContent({
className,
children,
...props
}: React.ComponentProps) {
return (
{children}
);
}
export { HoverCard, HoverCardTrigger, HoverCardContent };
```
# All Components (/docs/components)
## All Components
Here is a complete list of all available components in the Aura Design System:
### [Accordion](./components/accordion)
A vertically stacked set of interactive headings that each reveal a section of content.
### [Alert](./components/alert)
Displays a callout for user attention with contextual feedback messages.
### [Alert Dialog](./components/alert-dialog)
A modal dialog that interrupts the user with important content and expects a response.
### [Aspect Ratio](./components/aspect-ratio)
Displays content within a desired ratio.
### [Autocomplete](./components/autocomplete)
A input component that allows users to search and select from a list of options.
### [Avatar](./components/avatar)
An image element with a fallback for representing the user.
### [Badge](./components/badge)
Displays a badge or a component that looks like a badge.
### [Button](./components/button)
Displays a button or a component that looks like a button.
### [Button Group](./components/button-group)
A container that groups buttons (and optional text or separators) with shared styling and orientation.
### [Card](./components/card)
Displays a card with header, content, and footer sections.
### [Carousel](./components/carousel)
A carousel component built with Embla, with optional previous/next controls and configurable orientation.
### [Checkbox](./components/checkbox)
A control that allows the user to toggle between checked and not checked.
### [Collapsible](./components/collapsible)
An interactive component which expands and collapses content.
### [Combobox](./components/combobox)
A combobox component that allows users to search and select from a list of options.
### [Command](./components/command)
A command palette that allows users to search and execute commands.
### [Context Menu](./components/context-menu)
Displays a menu to the user triggered by right-click or long-press.
### [Dialog](./components/dialog)
A window overlaid on the primary content, rendering content in a layer above the page.
### [Drawer](./components/drawer)
A drawer component that slides in from the edge of the screen.
### [Dropdown Menu](./components/dropdown-menu)
Displays a menu of actions or options triggered by a button.
### [Empty](./components/empty)
A placeholder component for empty states, with optional icon, title, description, and content slots.
### [Form](./components/form)
A comprehensive form system with schema-based validation, error handling, and field management. Built with Radix UI primitives and AJV validation, supporting text inputs, textareas, selects, checkboxes, switches, and checkbox groups with automatic error propagation and touch state management.
### [Grid](./components/grid)
Displays a grid container with configurable column layout.
### [Hover Card](./components/hover-card)
For sighted users to preview content available behind a link.
### [Input](./components/input)
Displays a form input field or a component that looks like an input field.
### [Kbd](./components/kbd)
A keyboard shortcut component that allows users to display keyboard shortcuts.
### [Label](./components/label)
Renders an accessible label associated with form controls.
### [Menubar](./components/menubar)
A visually persistent menu common in desktop applications.
### [Navigation Menu](./components/navigation-menu)
A collection of links for navigating websites.
### [Popover](./components/popover)
Displays rich content in a portal triggered by a button.
### [Progress](./components/progress)
Displays an indicator showing the completion progress of a task.
### [Radio Group](./components/radio-group)
Re-usable components built using Radix UI and Tailwind CSS.
### [Scroll Area](./components/scroll-area)
Augments native scroll functionality for custom cross-browser styling.
### [Section](./components/section)
A semantic section container with configurable layout variants.
### [Select](./components/select)
A select component that allows users to select a value from a list of options.
### [Separator](./components/separator)
A separator component that allows users to separate content.
### [Sheet](./components/sheet)
A slide-out panel that overlays the page, with optional header, footer, title, and description. Opens from a trigger (e.g. button).
### [Sidebar](./components/sidebar)
A collapsible side panel for app navigation and layout, with optional mobile sheet, icons, and persistent state.
### [Signature Pad](./components/signature-pad)
A component that allows the user to sign a document using a pen.
### [Skeleton](./components/skeleton)
A loading placeholder with pulse animation, typically used for content that is being fetched.
### [Slider](./components/slider)
An input where the user selects a value from within a given range.
### [Sortable](./components/sortable)
A sortable list of items.
### [Stepper](./components/stepper)
A sequential progress indicator with navigation controls.
### [Switch](./components/switch)
A control that allows the user to toggle between on and off states.
### [Tabs](./components/tabs)
A tabs component that allows the user to switch between different views.
### [Toggle](./components/toggle)
A toggle component that allows the user to toggle between on and off states.
### [Toggle Group](./components/toggle-group)
A group of toggle buttons where one or more options can be selected, with single or multiple selection modes.
### [Tooltip](./components/tooltip)
A tooltip component that displays informative text when hovering over an element.
# Input (/docs/components/input)
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/input
```
### 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 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 };
```
# Kbd (/docs/components/kbd)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import { Kbd, KbdGroup } from "@/components/ui/Kbd";
import { ArrowUpIcon, ArrowDownIcon } from "@radix-ui/react-icons";
export function KbdDemo() {
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/kbd
```
### 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 Kbd component into your `components/ui/Kbd.tsx` file.
```tsx
import type * as React from "react";
import { cn } from "@/utils/class-names";
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
);
}
function KbdGroup({ className, ...props }: React.ComponentProps<"kbd">) {
return (
);
}
export { Kbd, KbdGroup };
```
## Usage
### SingleKey
```tsx
export const SingleKey = () => (
)
```
### Group
```tsx
export const Group = () => (
⌘K
)
```
# Label (/docs/components/label)
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/label
```
### 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 Label component into your `components/ui/Label.tsx` file.
```tsx
/**
* @description Renders an accessible label associated with form controls.
*/
import * as React from "react";
import { cn } from "@/utils/class-names";
function Label({ className, ...props }: React.ComponentProps<"label">) {
return ;
}
export { Label };
```
# Menubar (/docs/components/menubar)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSubContent,
MenubarSubTrigger,
MenubarSub,
MenubarShortcut,
} from "@/components/ui/Menubar";
export function MenubarDemo() {
return (
File
New Tab ⌘T
New Window ⌘NNew Incognito WindowShareEmail linkMessagesNotes
Print... ⌘PEdit
Undo ⌘Z
Redo ⇧⌘ZFindSearch the webFind...Find NextFind PreviousCutCopyPasteViewAlways Show Bookmarks BarAlways Show Full URLs
Reload ⌘R
Force Reload ⇧⌘RToggle FullscreenHide SidebarProfilesAndyBenoitLuisEdit...Add Profile...
)
}
```
## 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/menubar
```
### 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 Menubar component into your `components/ui/Menubar.tsx` file.
```tsx
"use client";
/**
* @description A visually persistent menu common in desktop applications.
*/
import * as React from "react";
import { Menubar as MenubarRadix } from "radix-ui";
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons";
import { cn } from "@/utils/class-names";
function Menubar({
className,
...props
}: React.ComponentProps) {
return (
);
}
function MenubarMenu({
...props
}: React.ComponentProps) {
return ;
}
function MenubarGroup({
...props
}: React.ComponentProps) {
return ;
}
function MenubarPortal({
...props
}: React.ComponentProps) {
return ;
}
function MenubarRadioGroup({
...props
}: React.ComponentProps) {
return ;
}
function MenubarTrigger({
className,
...props
}: React.ComponentProps) {
return (
);
}
function MenubarContent({
className,
...props
}: React.ComponentProps) {
return (
);
}
function MenubarItem({
className,
...props
}: React.ComponentProps) {
return (
);
}
function MenubarCheckboxItem({
children,
className,
...props
}: React.ComponentProps) {
return (
{children}
);
}
function MenubarRadioItem({
children,
className,
...props
}: React.ComponentProps) {
return (
{children}
);
}
function MenubarLabel({
className,
...props
}: React.ComponentProps) {
return (
);
}
function MenubarSeparator({
className,
...props
}: React.ComponentProps) {
return (
);
}
function MenubarShortcut({ ...props }: React.ComponentProps<"span">) {
return ;
}
function MenubarSub({
...props
}: React.ComponentProps) {
return ;
}
function MenubarSubTrigger({
children,
className,
...props
}: React.ComponentProps & {
inset?: boolean;
}) {
return (
{children}
);
}
function MenubarSubContent({
className,
...props
}: React.ComponentProps) {
return (
);
}
export {
Menubar,
MenubarPortal,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarGroup,
MenubarSeparator,
MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
};
```
# Navigation Menu (/docs/components/navigation-menu)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import React from "react";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
} from "@/components/ui/NavigationMenu";
export function NavigationMenuDemo() {
return {
return (
{/* Product Menu Item */}
Product
Overview
Features
Pricing
{/* Solutions Menu Item */}
Solutions
Enterprise
Small Business
{/* Company Menu Item (No Submenu) */}
Company
{/* Blog Menu Item (No Submenu) */}
Blog
);
};
```
## 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/navigation-menu
```
### 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 NavigationMenu component into your `components/ui/NavigationMenu.tsx` file.
```tsx
/**
* @description A collection of links for navigating websites.
*/
import * as React from "react";
import { NavigationMenu as NavigationMenuRadix } from "radix-ui";
import { cn } from "@/utils/class-names";
function NavigationMenu({
className,
children,
...props
}: React.ComponentProps) {
return (
{children}
);
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps) {
return (
);
}
function NavigationMenuItem({
className,
...props
}: React.ComponentProps) {
return (
);
}
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps) {
return (
{children}
);
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps) {
return (
);
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps) {
return (
);
}
function NavigationMenuLink({
className,
...props
}: React.ComponentProps) {
return (
);
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps) {
return (
);
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};
```
## Usage
### NavigationMenuDemo
```tsx
export function NavigationMenuDemo() {
return (
Home
Re-usable components built using Radix UI and Tailwind CSS.
How to install dependencies and structure your app.
Styles for headings, paragraphs, lists...etc
{(["top", "right", "bottom", "left"] as const).map((side) => (
Popover positioned on the {side} side.
))}
)
```
# Progress (/docs/components/progress)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import { useEffect, useState } from "react";
import { Progress } from "@/components/ui/Progress";
export function ProgressDemo() {
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/progress
```
### 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 Progress component into your `components/ui/Progress.tsx` file.
```tsx
"use client";
/**
* @description Displays an indicator showing the completion progress of a task.
*/
import * as React from "react";
import { Progress as ProgressRadix } from "radix-ui";
import { cn } from "@/utils/class-names";
function Progress({
className,
value,
...props
}: React.ComponentProps) {
return (
);
}
export { Progress };
```
## Usage
### Zero
```tsx
export const Zero = () => (
);
};
```
# Radio Group (/docs/components/radio-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 {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/RadioGroup";
export function RadioGroupDemo() {
return {
const [value, setValue] = useState("option-1");
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/radio-group
```
### 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 RadioGroup 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 };
```
## Usage
### WithDefaultValue
```tsx
export const WithDefaultValue = () => {
return (
);
};
```
# Skeleton (/docs/components/skeleton)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import { Skeleton } from "@/components/ui/Skeleton";
export function SkeletonDemo() {
return {
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/skeleton
```
### 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 Skeleton component into your `components/ui/Skeleton.tsx` file.
```tsx
import { cn } from "@/utils/class-names"
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
)
}
export { Skeleton }
```
# Slider (/docs/components/slider)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import { Slider } from "@/components/ui/Slider";
export function SliderDemo() {
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/slider
```
### 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 Slider component into your `components/ui/Slider.tsx` file.
```tsx
"use client"
/**
* @description An input where the user selects a value from within a given range.
*/
import * as React from "react"
import { Slider as SliderPrimitive } from "radix-ui"
import { cn } from "@/utils/class-names"
const Slider = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
Slider.displayName = SliderPrimitive.Root.displayName
export { Slider }
```
## Usage
### Disabled
```tsx
export const Disabled = () => (
);
};
```
## 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/stepper
```
### Manual
Install the following dependencies:
```bash
pnpm install @radix-ui/react-direction @radix-ui/react-icons @radix-ui/react-slot
```
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 compose refs utility into your `utils/compose-refs.tsx` file.
```tsx
/**
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx
*/
import * as React from "react";
type PossibleRef = React.Ref | undefined;
/**
* Set a given ref to a given value
* This utility takes care of different types of refs: callback refs and RefObject(s)
*/
function setRef(ref: PossibleRef, value: T) {
if (typeof ref === "function") {
return ref(value);
}
if (ref !== null && ref !== undefined) {
ref.current = value;
}
}
/**
* A utility to compose multiple refs together
* Accepts callback refs and RefObject(s)
*/
function composeRefs(...refs: PossibleRef[]): React.RefCallback {
return (node) => {
let hasCleanup = false;
const cleanups = refs.map((ref) => {
const cleanup = setRef(ref, node);
if (!hasCleanup && typeof cleanup === "function") {
hasCleanup = true;
}
return cleanup;
});
// React <19 will log an error to the console if a callback ref returns a
// value. We don't use ref cleanups internally so this will only happen if a
// user's ref callback returns a value, which we only expect if they are
// using the cleanup functionality added in React 19.
if (hasCleanup) {
return () => {
for (let i = 0; i < cleanups.length; i++) {
const cleanup = cleanups[i];
if (typeof cleanup === "function") {
cleanup();
} else {
setRef(refs[i], null);
}
}
};
}
};
}
/**
* A custom hook that composes multiple refs
* Accepts callback refs and RefObject(s)
*/
function useComposedRefs(...refs: PossibleRef[]): React.RefCallback {
// biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values
return React.useCallback(composeRefs(...refs), refs);
}
export { composeRefs, useComposedRefs };
```
Copy and paste the use as ref hook into your `hooks/use-as-ref.ts` file.
```ts
import * as React from "react";
import { useIsomorphicLayoutEffect } from "@/hooks/use-isomorphic-layout-effect";
function useAsRef(props: T) {
const ref = React.useRef(props);
useIsomorphicLayoutEffect(() => {
ref.current = props;
});
return ref;
}
export { useAsRef };
```
Copy and paste the use isomorphic layout effect hook into your `hooks/use-isomorphic-layout-effect.ts` file.
```ts
import * as React from "react";
const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
export { useIsomorphicLayoutEffect };
```
Copy and paste the use lazy ref hook into your `hooks/use-lazy-ref.ts` file.
```ts
import * as React from "react";
function useLazyRef(fn: () => T) {
const ref = React.useRef(null);
if (ref.current === null) {
ref.current = fn();
}
return ref as React.RefObject;
}
export { useLazyRef };
```
Copy and paste the Stepper component into your `components/ui/Stepper.tsx` file.
```tsx
"use client";
import { useDirection } from "@radix-ui/react-direction";
import { Slot } from "@radix-ui/react-slot";
import { CheckIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { useComposedRefs } from "@/utils/compose-refs";
import { cn } from "@/utils/class-names";
import { useAsRef } from "@/hooks/use-as-ref";
import { useIsomorphicLayoutEffect } from "@/hooks/use-isomorphic-layout-effect";
import { useLazyRef } from "@/hooks/use-lazy-ref";
const ROOT_NAME = "Stepper";
const LIST_NAME = "StepperList";
const ITEM_NAME = "StepperItem";
const TRIGGER_NAME = "StepperTrigger";
const INDICATOR_NAME = "StepperIndicator";
const SEPARATOR_NAME = "StepperSeparator";
const TITLE_NAME = "StepperTitle";
const DESCRIPTION_NAME = "StepperDescription";
const CONTENT_NAME = "StepperContent";
const PREV_NAME = "StepperPrev";
const NEXT_NAME = "StepperNext";
const ENTRY_FOCUS = "stepperFocusGroup.onEntryFocus";
const EVENT_OPTIONS = { bubbles: false, cancelable: true };
const ARROW_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];
type Direction = "ltr" | "rtl";
type Orientation = "horizontal" | "vertical";
type NavigationDirection = "next" | "prev";
type ActivationMode = "automatic" | "manual";
type DataState = "inactive" | "active" | "completed";
interface DivProps extends React.ComponentProps<"div"> {
asChild?: boolean;
}
interface ButtonProps extends React.ComponentProps<"button"> {
asChild?: boolean;
}
type ListElement = React.ComponentRef;
type TriggerElement = React.ComponentRef;
function getId(
id: string,
variant: "trigger" | "content" | "title" | "description",
value: string,
) {
return `${id}-${variant}-${value}`;
}
type FocusIntent = "first" | "last" | "prev" | "next";
const MAP_KEY_TO_FOCUS_INTENT: Record = {
ArrowLeft: "prev",
ArrowUp: "prev",
ArrowRight: "next",
ArrowDown: "next",
PageUp: "first",
Home: "first",
PageDown: "last",
End: "last",
};
function getDirectionAwareKey(key: string, dir?: Direction) {
if (dir !== "rtl") return key;
return key === "ArrowLeft"
? "ArrowRight"
: key === "ArrowRight"
? "ArrowLeft"
: key;
}
function getFocusIntent(
event: React.KeyboardEvent,
dir?: Direction,
orientation?: Orientation,
) {
const key = getDirectionAwareKey(event.key, dir);
if (orientation === "horizontal" && ["ArrowUp", "ArrowDown"].includes(key))
return undefined;
if (orientation === "vertical" && ["ArrowLeft", "ArrowRight"].includes(key))
return undefined;
return MAP_KEY_TO_FOCUS_INTENT[key];
}
function focusFirst(
candidates: React.RefObject[],
preventScroll = false,
) {
const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
for (const candidateRef of candidates) {
const candidate = candidateRef.current;
if (!candidate) continue;
if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
candidate.focus({ preventScroll });
if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
}
}
function wrapArray(array: T[], startIndex: number) {
return array.map(
(_, index) => array[(startIndex + index) % array.length] as T,
);
}
function getDataState(
value: string | undefined,
itemValue: string,
stepState: StepState | undefined,
steps: Map,
variant: "item" | "separator" = "item",
): DataState {
const stepKeys = Array.from(steps.keys());
const currentIndex = stepKeys.indexOf(itemValue);
if (stepState?.completed) return "completed";
if (value === itemValue) {
return variant === "separator" ? "inactive" : "active";
}
if (value) {
const activeIndex = stepKeys.indexOf(value);
if (activeIndex > currentIndex) return "completed";
}
return "inactive";
}
interface StepState {
value: string;
completed: boolean;
disabled: boolean;
}
interface StoreState {
steps: Map;
value: string;
}
interface Store {
subscribe: (callback: () => void) => () => void;
getState: () => StoreState;
setState: (key: K, value: StoreState[K]) => void;
setStateWithValidation: (
value: string,
direction: NavigationDirection,
) => Promise;
hasValidation: () => boolean;
notify: () => void;
addStep: (value: string, completed: boolean, disabled: boolean) => void;
removeStep: (value: string) => void;
setStep: (value: string, completed: boolean, disabled: boolean) => void;
}
const StoreContext = React.createContext(null);
function useStoreContext(consumerName: string) {
const context = React.useContext(StoreContext);
if (!context) {
throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
}
return context;
}
function useStore(selector: (state: StoreState) => T): T {
const store = useStoreContext("useStore");
const getSnapshot = React.useCallback(
() => selector(store.getState()),
[store, selector],
);
return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
}
interface ItemData {
id: string;
ref: React.RefObject;
value: string;
active: boolean;
disabled: boolean;
}
interface StepperContextValue {
rootId: string;
dir: Direction;
orientation: Orientation;
activationMode: ActivationMode;
disabled: boolean;
nonInteractive: boolean;
loop: boolean;
}
const StepperContext = React.createContext(null);
function useStepperContext(consumerName: string) {
const context = React.useContext(StepperContext);
if (!context) {
throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
}
return context;
}
interface StepperProps extends DivProps {
value?: string;
defaultValue?: string;
onValueChange?: (value: string) => void;
onValueComplete?: (value: string, completed: boolean) => void;
onValueAdd?: (value: string) => void;
onValueRemove?: (value: string) => void;
onValidate?: (
value: string,
direction: NavigationDirection,
) => boolean | Promise;
activationMode?: ActivationMode;
dir?: Direction;
orientation?: Orientation;
disabled?: boolean;
loop?: boolean;
nonInteractive?: boolean;
}
function Stepper(props: StepperProps) {
const {
value,
defaultValue,
onValueChange,
onValueComplete,
onValueAdd,
onValueRemove,
onValidate,
dir: dirProp,
orientation = "horizontal",
activationMode = "automatic",
asChild,
disabled = false,
nonInteractive = false,
loop = false,
className,
id,
...rootProps
} = props;
const listenersRef = useLazyRef(() => new Set<() => void>());
const stateRef = useLazyRef(() => ({
steps: new Map(),
value: value ?? defaultValue ?? "",
}));
const propsRef = useAsRef({
onValueChange,
onValueComplete,
onValueAdd,
onValueRemove,
onValidate,
});
const store = React.useMemo(() => {
return {
subscribe: (cb) => {
listenersRef.current.add(cb);
return () => listenersRef.current.delete(cb);
},
getState: () => stateRef.current,
setState: (key, value) => {
if (Object.is(stateRef.current[key], value)) return;
if (key === "value" && typeof value === "string") {
stateRef.current.value = value;
propsRef.current.onValueChange?.(value);
} else {
stateRef.current[key] = value;
}
store.notify();
},
setStateWithValidation: async (value, direction) => {
if (!propsRef.current.onValidate) {
store.setState("value", value);
return true;
}
try {
const isValid = await propsRef.current.onValidate(value, direction);
if (isValid) {
store.setState("value", value);
}
return isValid;
} catch {
return false;
}
},
hasValidation: () => !!propsRef.current.onValidate,
addStep: (value, completed, disabled) => {
const newStep: StepState = { value, completed, disabled };
stateRef.current.steps.set(value, newStep);
propsRef.current.onValueAdd?.(value);
store.notify();
},
removeStep: (value) => {
stateRef.current.steps.delete(value);
propsRef.current.onValueRemove?.(value);
store.notify();
},
setStep: (value, completed, disabled) => {
const step = stateRef.current.steps.get(value);
if (step) {
const updatedStep: StepState = { ...step, completed, disabled };
stateRef.current.steps.set(value, updatedStep);
if (completed !== step.completed) {
propsRef.current.onValueComplete?.(value, completed);
}
store.notify();
}
},
notify: () => {
for (const cb of listenersRef.current) {
cb();
}
},
};
}, [listenersRef, stateRef, propsRef]);
useIsomorphicLayoutEffect(() => {
if (value !== undefined) {
store.setState("value", value);
}
}, [value]);
const dir = useDirection(dirProp);
const instanceId = React.useId();
const rootId = id ?? instanceId;
const contextValue = React.useMemo(
() => ({
rootId,
dir,
orientation,
activationMode,
disabled,
nonInteractive,
loop,
}),
[rootId, dir, orientation, activationMode, disabled, nonInteractive, loop],
);
const RootPrimitive = asChild ? Slot : "div";
return (
);
}
interface FocusContextValue {
tabStopId: string | null;
onItemFocus: (tabStopId: string) => void;
onItemShiftTab: () => void;
onFocusableItemAdd: () => void;
onFocusableItemRemove: () => void;
onItemRegister: (item: ItemData) => void;
onItemUnregister: (id: string) => void;
getItems: () => ItemData[];
}
const FocusContext = React.createContext(null);
function useFocusContext(consumerName: string) {
const context = React.useContext(FocusContext);
if (!context) {
throw new Error(
`\`${consumerName}\` must be used within \`FocusProvider\``,
);
}
return context;
}
interface StepperListProps extends DivProps {
asChild?: boolean;
}
function StepperList(props: StepperListProps) {
const {
asChild,
onBlur: onBlurProp,
onFocus: onFocusProp,
onMouseDown: onMouseDownProp,
className,
children,
ref,
...listProps
} = props;
const context = useStepperContext(LIST_NAME);
const orientation = context.orientation;
const currentValue = useStore((state) => state.value);
const propsRef = useAsRef({
onBlur: onBlurProp,
onFocus: onFocusProp,
onMouseDown: onMouseDownProp,
});
const [tabStopId, setTabStopId] = React.useState(null);
const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);
const [focusableItemCount, setFocusableItemCount] = React.useState(0);
const isClickFocusRef = React.useRef(false);
const itemsRef = React.useRef
# Switch (/docs/components/switch)
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 { Switch } from "@/components/ui/Switch";
export function SwitchDemo() {
return {
const [checked, setChecked] = useState(false);
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/switch
```
### 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 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 };
```
## Usage
### Checked
```tsx
export const Checked = () => (
handleCheckedChange(item.id as keyof typeof settings, checked)
}
className="mt-0.5"
/>
{item.description}
))}
Active settings: {Object.values(settings).filter(Boolean).length}
);
};
```
# Tabs (/docs/components/tabs)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "@/components/ui/Tabs";
export function TabsDemo() {
return {
return (
AccountPassword
Make changes to your account here.
Change your password here.
);
};
```
## 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/tabs
```
### 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 Tabs component into your `components/ui/Tabs.tsx` file.
```tsx
"use client"
import * as React from "react"
import {Tabs as TabsPrimitive} from "radix-ui"
import { cn } from "@/utils/class-names"
function Tabs({
className,
...props
}: React.ComponentProps) {
return (
)
}
function TabsList({
className,
...props
}: React.ComponentProps) {
return (
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps) {
return (
)
}
function TabsContent({
className,
...props
}: React.ComponentProps) {
return (
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }
```
# Toggle Group (/docs/components/toggle-group)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/ToggleGroup";
import {
TextAlignLeftIcon,
TextAlignCenterIcon,
TextAlignRightIcon,
} from "@radix-ui/react-icons";
export function ToggleGroupDemo() {
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/toggle-group
```
### Manual
Install the following dependencies:
```bash
pnpm install class-variance-authority 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 ToggleGroup component into your `components/ui/ToggleGroup.tsx` file.
```tsx
"use client";
import * as React from "react";
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/utils/class-names";
const toggleGroupVariants = cva(
"inline-flex items-center justify-center border border-gray-6 rounded-sm bg-gray-2",
{
variants: {
variant: {
default: "",
},
},
defaultVariants: {
variant: "default",
},
}
);
const toggleGroupItemVariants = cva(
"inline-flex items-center justify-center gap-2 data-[state=on]:bg-gray-4 data-[state=on]:hover:bg-gray-3",
{
variants: {
variant: {
default:
"button-pill border border-transparent text-gray-11 bg-gray-2 hover:bg-gray-3 rounded-sm",
},
size: {
default: "w-3 h-3 p-0",
xs: "h-2.5",
sm: "h-3",
md: "h-4",
lg: "h-5",
xl: "h-6",
icon: "w-3 h-3 p-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
function ToggleGroup({
className,
variant,
...props
}: React.ComponentProps &
VariantProps) {
return (
);
}
function ToggleGroupItem({
className,
variant,
size,
...props
}: React.ComponentProps &
VariantProps) {
return (
);
}
export { ToggleGroup, ToggleGroupItem, toggleGroupVariants, toggleGroupItemVariants };
```
# Toggle (/docs/components/toggle)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import { Toggle } from "@/components/ui/Toggle";
import {
FontItalicIcon,
HeartIcon,
HeartFilledIcon,
} from "@radix-ui/react-icons";
export function ToggleDemo() {
return {
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/toggle
```
### Manual
Install the following dependencies:
```bash
pnpm install class-variance-authority 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 Toggle component into your `components/ui/Toggle.tsx` file.
```tsx
"use client";
import * as React from "react";
import { Toggle as TogglePrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/utils/class-names";
const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 data-[state=on]:bg-gray-4 data-[state=on]:hover:bg-gray-3",
{
variants: {
variant: {
default:
"button-pill border border-gray-6 text-gray-11 bg-gray-2 hover:bg-gray-3",
},
size: {
default: "w-3 h-3 p-0",
xs: "h-2.5",
sm: "h-3",
md: "h-4",
lg: "h-5",
xl: "h-6",
icon: "w-3 h-3 p-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
function Toggle({
className,
variant,
size,
...props
}: React.ComponentProps &
VariantProps) {
return (
);
}
export { Toggle, toggleVariants };
```
## Usage
### ToggleIcons
```tsx
export const ToggleIcons = () => {
return (
);
};
```
# Tooltip (/docs/components/tooltip)
import { ComponentPreview } from "@/components/ComponentPreview"
import { ComponentSource } from "@/components/ComponentSource"
import { Steps, Step } from "fumadocs-ui/components/steps"
## Preview
```tsx
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/Tooltip";
import { Button } from "@/components/ui/Button";
export function TooltipDemo() {
return (
This is a tooltip
)
}
```
## 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/tooltip
```
### 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 Tooltip component into your `components/ui/Tooltip.tsx` file.
```tsx
"use client"
import * as React from "react"
import { Tooltip as TooltipPrimitive } from "radix-ui"
import { cn } from "@/utils/class-names"
function TooltipProvider({
delayDuration = 0,
...props
}: React.ComponentProps) {
return (
)
}
function Tooltip({
...props
}: React.ComponentProps) {
return (
)
}
function TooltipTrigger({
...props
}: React.ComponentProps) {
return
}
function TooltipContent({
className,
sideOffset = 0,
children,
...props
}: React.ComponentProps) {
return (
{children}
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
```
# Form Components (/docs/forms/form-components)
# Form Components
The form UI is built on Radix UI Form primitives. These components consume `field` props from [useFormDynamic](/docs/forms/use-dynamic-form) and, when used inside `Form`, receive `errors` automatically so validation messages show on the right fields.
## Form
The root wrapper. It forwards all props to Radix Form and adds error distribution.
**Props:** Extends Radix `Form.Root` (e.g. `onSubmit`, `id`, `className`, `ref`). In addition:
| Prop | Type | Description |
| -------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `errors` | `ErrorObject, unknown>[]` (from AJV) | Validation errors. Form matches each error to a child by `error.instancePath.slice(1) === child.props.field?.name` and injects that slice of errors into the child. |
**Behavior:** Form walks its children (recursively). For any descendant that has a `field` prop, it filters `errors` to those whose `instancePath` (after removing the leading `/`) equals `field.name`, then clones that descendant with an `errors` prop. So you don’t pass `errors` to each field component yourself — Form does it.
```tsx
```
## FormSubmit
Submit button wired to the form, with optional loading state.
| Prop | Type | Description |
| ------------- | --------------------------------------------- | ---------------------------------------------------------------------- |
| `buttonProps` | `ButtonProps` | Props passed to the underlying Button (e.g. `children`, `className`). |
| `fetchStatus` | `"idle" \| "loading" \| "success" \| "error"` | When `"loading"`, the button shows a loading indicator (e.g. spinner). |
| `form` | `string` | Must match the `id` of the form (for native `form` attribute). |
| (others) | Radix `Form.Submit` props | e.g. `disabled`. |
```tsx
```
## FormAlert
Shows a form-level error when submit fails or validation fails and you set a message.
| Prop | Type | Description |
| ---------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
| `formData` | `{ fetchStatus: string; error: string \| null }` | Renders only when `fetchStatus === "error"` and `formData.error` is set. |
Use it with the hook’s `fetchStatus` and `error`:
```tsx
```
## FormField
Generic wrapper for a single native control (input, textarea, or select). Renders a label, the control, and per-field validation messages.
| Prop | Type | Description |
| -------------- | ------------------------ | --------------------------------------------------------------------------- |
| `field` | `FieldProps` | From `formData.getFields()` or `formData.field(name)`. |
| `label` | `React.ReactNode` | Label text or node. |
| `labelProps` | Radix Form.Label props | Optional. |
| `controlProps` | Radix Form.Control props | Optional. |
| `errors` | `ErrorObject[]` | Usually injected by Form; only set manually if the field is not under Form. |
The control receives `field.onChange` from Form.Control. Use for ``, `