# The asChild prop (/docs/as-child) The `asChild` prop lets you swap the default element for your own—a link, a custom button, whatever—while keeping the component’s styles, props, and behavior. You get composition without extra wrapper divs or duplicated markup. That’s useful when you need semantic HTML (like an anchor for navigation) but want it to look and act like a Button or Badge. Creating new Components [#creating-new-components] When you build custom components with the [Ark factory](https://ark-ui.com/docs/guides/composition#the-ark-factory), they automatically support `asChild` because Ark’s factory handles the prop under the hood. You don’t have to do anything special—just pass through props and the child merges in: ```tsx import { ark } from '@ark-ui/react/factory' interface ButtonExampleProps extends React.ComponentProps {} export const ButtonExample = (props: ButtonExampleProps) => ( ) ``` ```tsx import Link from "next/link"; Login ``` Examples [#examples] Link [#link] Need a login link that looks like a button? Use `asChild` so the `Link` becomes the rendered element—no extra div, and it keeps proper semantics for SEO and screen readers. ```tsx showLineNumbers ``` Badge [#badge] Sometimes a badge is clickable—a tag that goes somewhere, or a category filter. Wrap a `Link` with `Badge` and `asChild`, and the link gets the badge styling while staying a real anchor. ```tsx showLineNumbers React ``` Dialog [#dialog] Trigger components like `DialogTrigger` often need a button. Instead of nesting a button inside a trigger div, pass the `Button` as the child and let it become the trigger. Same accessibility, less markup. ```tsx showLineNumbers {/* ... */} ``` Menu [#menu] Same pattern for menus. The `Button` merges into `MenuTrigger`, so you get a single element that opens the menu with the right keyboard and focus behavior. ```tsx showLineNumbers {/* ... */} ``` Popover [#popover] Popovers work the same way. Use `asChild` on `PopoverTrigger` and pass your `Button`—no wrapper needed. ```tsx showLineNumbers {/* ... */} ``` When to use it [#when-to-use-it] Reach for `asChild` when you want the right element in the DOM: links for navigation, buttons for triggers, or any case where semantic HTML matters. It’s handy for forms (submit links styled as buttons), navigation bars, and anywhere you’d otherwise wrap something in a div just to apply styles. # Colors (/docs/colors) Shark UI relies on CSS custom properties for its color system. For layout setup and the full styling guide, see [Styling](/docs/styling). These variables toggle between light and dark themes automatically when you add the `dark` class to your root element. All definitions live in `globals.css` and stick to the [shadcn/ui](https://ui.shadcn.com) naming style. How It Works [#how-it-works] Colors are wired up through Tailwind's theme. The `@theme inline` block in `globals.css` maps semantic variables into Tailwind utilities so you can use classes like `bg-primary` and `text-muted-foreground`. Naming rules: * Names with `foreground` define text (or icon) color on top of that background (e.g. `--primary-foreground`). * Names without `foreground` define backgrounds (e.g. `--primary`, `--card`, `--popover`). Typical usage: ```tsx {/* Your app */} ``` ```tsx
Primary action
``` Variable Reference [#variable-reference] Base Layout [#base-layout] * `--background` — Page background * `--foreground` — Default text on that background Cards and Surfaces [#cards-and-surfaces] * `--card` / `--card-foreground` — Card backgrounds and their text * `--popover` / `--popover-foreground` — Popovers, dropdowns, menus Primary Actions [#primary-actions] * `--primary` / `--primary-foreground` — Main buttons and links Secondary and Muted [#secondary-and-muted] * `--secondary` / `--secondary-foreground` — Secondary controls * `--muted` / `--muted-foreground` — Softer backgrounds and de‑emphasized text * `--accent` / `--accent-foreground` — Hover and active states Status Colors [#status-colors] * `--success` / `--success-foreground` * `--info` / `--info-foreground` * `--warning` / `--warning-foreground` * `--destructive` / `--destructive-foreground` Form and UI [#form-and-ui] * `--border` — Borders * `--input` — Input backgrounds * `--ring` — Focus rings Sidebar [#sidebar] * `--sidebar` — Sidebar background * `--sidebar-foreground` — Sidebar text * `--sidebar-primary` — Active item background * `--sidebar-accent` — Hover and active states * `--sidebar-border` — Sidebar borders * `--sidebar-ring` — Focus rings Charts [#charts] * `--chart-1` — Chart series color 1 * `--chart-2` — Chart series color 2 * `--chart-3` — Chart series color 3 * `--chart-4` — Chart series color 4 * `--chart-5` — Chart series color 5 How to Use Colors [#how-to-use-colors] In JSX with Tailwind classes: ```tsx
``` For full setup, see the [manual installation guide](/docs/installation/manual). Customizing Colors [#customizing-colors] Override variables in `:root` for light mode and `.dark` for dark mode: ```css :root { --primary: var(--color-blue-600); --primary-foreground: var(--color-blue-50); } .dark { --primary: var(--color-blue-500); --primary-foreground: var(--color-blue-50); } ``` For oklch or custom values, tools like [oklch.com](https://oklch.com/) help with conversion. Shark UI ships preset themes, check [Theming](/docs/theming) for more information. Quick Tips [#quick-tips] * Apply the `dark` class on the root so dark mode styles take effect. * Check both light and dark appearances when changing colors. * Keep using foreground/background pairs so contrast stays safe. * Prefer color variables over hardcoded hex or rgb values. # Forms (/docs/forms) Shark UI builds on Ark UI's `Field` and `Fieldset` components for integrating with native `form` element or popular form libraries like [React Hook Form](https://react-hook-form.com/) and [TanStack Form](https://tanstack.com/form/latest). Form Libraries [#form-libraries] For detailed guides with examples, validation, and field-type-specific patterns, see the linked pages above. Field Context [#field-context] Form components in Shark UI automatically integrate with `Field` through context. When nested inside a `Field`, they inherit `disabled`, `invalid`, `required`, and `readOnly` states automatically. ```tsx import { Field } from "@/registry/react/components/field" import { NumberField } from "@/registry/react/components/number-input" const Demo = () => ( ) ``` Accessible Labels [#accessible-labels] When building accessible forms, you need to ensure that they are properly labeled and described. * `FieldHelper`: Used to provide additional instructions about the input. * `FieldLabel`: Used to provide an accessible label for the input. These components are automatically linked to the input element via the `aria-describedby` attribute. > **Best practice:** Make sure that labels are visible (and not just used as placeholders) for screen readers to read them. ```tsx import { Field, FieldLabel, FieldHelper, } from "@/registry/react/components/field" import { Input } from "@/registry/react/components/input" const Demo = () => (
Username This will be your public display name.
) ``` Error Handling and Validation [#error-handling-and-validation] When the input is invalid, you can use the `FieldError` component to provide an error message for the input, and pass the `invalid` prop to the `Field` component. > **Best practice:** Make sure to provide clear, specific error messages that are easy to understand and fix. ```tsx import { Field, FieldLabel, FieldError, } from "@/registry/react/components/field" import { Input } from "@/registry/react/components/input" const Demo = () => (
Username Username is required.
) ``` Required Fields [#required-fields] To indicate that a field is required, you can pass the `required` prop to the `Field` component. Optionally, you can use the `` to indicate that the field is required. ```tsx import { Field, FieldLabel, FieldRequiredIndicator, FieldError, } from "@/registry/react/components/field" import { Input } from "@/registry/react/components/input" export const Demo = () => (
Username (required) Username is required.
) ``` Fieldset Context [#fieldset-context] When you have multiple fields in a form or a component that renders multiple `input` elements, you can use the `FieldSet` component to group them together. Common use cases: checkbox group, radio group, input + select composition, etc. Checkbox Group [#checkbox-group] ```tsx import { FieldSet, FieldLegend, FieldDescription, FieldGroup, Field, FieldLabel, } from "@/registry/react/components/field" import { Checkbox, CheckboxGroup } from "@/registry/react/components/checkbox" const items = [ { label: "React", value: "react" }, { label: "Solid", value: "solid" }, { label: "Vue", value: "vue" }, { label: "Svelte", value: "svelte" }, ] const Demo = () => (
Frameworks Choose your preferred frameworks {items.map((item) => ( {item.label} ))}
) ``` Radio Group [#radio-group] ```tsx import { FieldSet, FieldLegend, FieldDescription, } from "@/registry/react/components/field" import { RadioGroup, RadioGroupItem } from "@/registry/react/components/radio-group" const items = [ { label: "React", value: "react" }, { label: "Solid", value: "solid" }, { label: "Vue", value: "vue" }, { label: "Svelte", value: "svelte" }, ] const Demo = () => (
Frameworks Choose your preferred framework {items.map((item) => ( {item.label} ))}
) ``` # Introduction (/docs) What is Shark UI? [#what-is-shark-ui] Shark UI is an opinionated UI library built on top of [Ark UI](https://ark-ui.com) and [shadcn](https://shadcn.com). "Opinionated" means it comes with sensible defaults out of the box—styling, variants, and structure—so you can drop components into a project without starting from scratch. It’s not a black-box framework; you own the code, tweak what you need, and build on top of something that’s already thought through. The name blends the two projects it’s built on: **sh** from shadcn and **ark** from Ark UI. Shark. Motivation [#motivation] [Ark UI](https://ark-ui.com) offers a broad set of headless components—made by the team behind [Chakra UI](https://chakra-ui.com)—and serves as a direct alternative to [Radix Primitives](https://radix-ui.com/primitives) and [Base UI](https://base-ui.com). It gives you accessible primitives and state management, but it leaves styling and layout entirely to you. [shadcn](https://shadcn.com), on the other hand, ships copy-paste components that look great with [Tailwind CSS](https://tailwindcss.com). The gap is that Ark’s docs don’t show how to pair its primitives with Tailwind, or how to turn them into a reusable design system you can install, theme, and extend. If you wanted that combination, you had to figure it out yourself. That’s where Shark UI comes in. Solution [#solution] Shark UI bridges that gap. It takes Ark’s headless components, styles them with Tailwind using shadcn’s patterns, and gives you a design system you can install, customize, and grow. Components live in your project, not in a dependency you can’t touch. You get Tailwind examples, consistent theming, and a structure that scales—without reinventing the wheel. Installation [#installation] To get started, follow the [installation guide](/docs/installation). You’ll choose your framework (Next.js, Vite, Astro, and others are supported), add the components you need, and you’re off. # LLMs.txt (/docs/llms-txt) Shark UI provides [LLMs.txt](https://llmstxt.org/) endpoints so AI coding assistants can fetch documentation directly by URL. Available Files [#available-files] **Core documentation:** * [/llms.txt](/llms.txt) — Quick reference index for documentation * [/llms-full.txt](/llms-full.txt) — Complete Shark UI documentation **For limited context windows:** * [/llms-components.txt](/llms-components.txt) — Component documentation only * [/llms-patterns.txt](/llms-patterns.txt) — Common patterns and recipes **All platforms:** * [/llms.txt](/llms.txt) — Quick reference index * [/llms-full.txt](/llms-full.txt) — Complete documentation * [/llms-components.txt](/llms-components.txt) — All component documentation * [/llms-patterns.txt](/llms-patterns.txt) — All patterns and recipes Integration [#integration] **Claude Code:** Reference the docs in your prompt or add to your project’s `.claude` file: ```bash Use Shark UI documentation from https://shark.vini.one/llms.txt ``` **Cursor:** Use the `@Docs` feature: ```bash @Docs https://shark.vini.one/llms-full.txt ``` [Learn more](https://docs.cursor.com/context/@-symbols/@-docs) **Windsurf:** Add to your `.windsurfrules` file: ```bash #docs https://shark.vini.one/llms-full.txt ``` [Learn more](https://docs.codeium.com/windsurf/memories#memories-and-rules) **Other AI tools:** Most assistants accept documentation URLs: ```bash https://shark.vini.one/llms.txt ``` For component-specific documentation: ```bash https://shark.vini.one/llms-components.txt ``` For patterns and best practices: ```bash https://shark.vini.one/llms-patterns.txt ``` Contributing [#contributing] Spotted an issue with AI-generated code? Contributions to improve the LLMs.txt output are welcome on [GitHub](https://github.com/vinihvc/shark-ui). # RTL (/docs/rtl) Setup [#setup] Wrap your app with `LocaleProvider` and pass an RTL locale. Use this when you know the user’s language ahead of time or when the app switches locale dynamically. ```tsx showLineNumbers {4,6} import { LocaleProvider } from "@/components/ui/locale"; export const App = () => ( {/* Your app */} ); ``` Common RTL locales include `ar-BH`, `ar-SA`, `he-IL`, and `fa-IR`. > **Note:** If no `LocaleProvider` is set up, the default locale is `en-US` and the direction stays `ltr`. Applying Direction [#applying-direction] Read the current `dir` from `useLocale` and pass it to your root element—usually ``—so the layout renders correctly and child components inherit the right text direction. ```tsx import { LocaleProvider, useLocale } from "@/components/ui/locale"; const AppContent = () => { const { locale, dir } = useLocale(); return ( {/* Your app content */} ); }; ``` How it works [#how-it-works] Shark UI components rely on logical CSS properties wherever possible. That means spacing and layout use `ms-*`, `me-*`, `ps-*`, `pe-*`, and `start-*` / `end-*` instead of physical ones like `ml-*`, `mr-*`, `left-*`, or `right-*`. When you flip `dir` to `rtl`, the layout adapts automatically because those utilities follow the text direction. Setting `dir={dir}` on the root ensures that direction propagates down. Ark UI’s overlays, tooltips, and menus respect the document direction, so they position themselves correctly whether the user reads left-to-right or right-to-left. Animations [#animations] Animations should follow the reading direction. Physical utilities like `slide-in-from-right` will slide the wrong way in RTL. Use logical alternatives so motion stays consistent: * `slide-in-from-end` / `slide-out-to-end` instead of `slide-in-from-right` / `slide-out-to-right` * `slide-in-from-start` / `slide-out-to-start` instead of `slide-in-from-left` / `slide-out-to-left` *** For the full API reference, see the [Ark UI documentation](https://ark-ui.com/docs/utilities/locale). # Skills (/docs/skills) Skills give AI assistants like Claude Code project-aware context about Shark UI. When installed, your AI assistant knows how to find, install, compose, and customize components using the correct APIs and patterns for your project. For example, you can ask your AI assistant to: * "Add a login form with email and password fields." * "Create a settings page with a form for updating profile information." * "Build a dashboard with a sidebar, stats cards, and a data table." * "Add the dialog component from the Shark registry." * "Can you add a hero block from the Shark blocks?" Setup [#setup] Run the installer script (works with Claude Code, Cursor, OpenCode, Codex, and Antigravity): ```bash curl -fsSL https://shark.vini.one/install | bash -s shark-ui ``` Alternatively, install via the skills CLI: ```bash npx skills add shark-ui ``` Using Skills [#using-skills] Agents discover skills automatically, or you can invoke the skill with `/shark-ui`. You can instruct your assistant to: * Implement UIs with Shark UI components * Create screens and flows using the component set * Adjust themes, tokens, and styling * Look up component APIs and examples For broader AI context, see [llms.txt](/docs/llms-txt) for documentation bundled for agents. What You Get [#what-you-get] * Shark UI setup and installation instructions * Component catalog with props, variants, and usage * Theming and styling notes * Design principles and composition patterns Skill Layout [#skill-layout] ```bash skills/shark-ui/ ├── SKILL.md # Skill entry point ├── LICENSE.txt └── scripts/ ├── list_components.mjs ├── get_component_docs.mjs ├── get_source.mjs ├── get_styles.mjs ├── get_theme.mjs └── get_docs.mjs ``` # Styling (/docs/styling) Overview [#overview] Everything here is driven by CSS variables, which is what keeps borders crisp and stacking clear on screen. If you need the token list and how colors map to utilities, read [Colors](/docs/colors). Borders are deliberately a little see-through and sit next to light shadows so surfaces read as separate layers. You can swap in your own palette, but the defaults are balanced for that recipe—typical flat themes often end up with muddier edges or flatter depth. Installation [#installation] See [Get Started](/docs/installation) for setup. To add the theme yourself, paste the following into `globals.css`: ```css @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-info: var(--info); --color-info-foreground: var(--info-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); --radius-xs: calc(var(--radius) * 0.25); --radius-sm: calc(var(--radius) * 0.5); --radius-md: calc(var(--radius) * 0.75); --radius-lg: calc(var(--radius) * 1); --radius-xl: calc(var(--radius) * 1.5); --radius-2xl: calc(var(--radius) * 2); --radius-3xl: calc(var(--radius) * 3); --radius-4xl: calc(var(--radius) * 4); } :root { --radius: 0.5rem; --background: var(--color-neutral-50); --foreground: var(--color-neutral-800); --card: var(--color-neutral-50); --card-foreground: var(--color-neutral-800); --popover: var(--color-neutral-50); --popover-foreground: var(--color-neutral-800); --primary: var(--color-neutral-800); --primary-foreground: var(--color-neutral-50); --secondary: color-mix( in srgb, var(--color-neutral-950) 4%, var(--background) ); --secondary-foreground: var(--color-neutral-800); --muted: color-mix(in srgb, var(--color-neutral-950) 4%, var(--background)); --muted-foreground: color-mix( in srgb, var(--color-neutral-500) 90%, var(--color-neutral-950) ); --accent: color-mix(in srgb, var(--color-neutral-950) 4%, var(--background)); --accent-foreground: var(--color-neutral-800); --destructive: var(--color-red-500); --destructive-foreground: var(--color-red-700); --info: var(--color-blue-500); --info-foreground: var(--color-blue-700); --success: var(--color-emerald-500); --success-foreground: var(--color-emerald-700); --warning: var(--color-amber-500); --warning-foreground: var(--color-amber-700); --border: color-mix(in srgb, var(--color-neutral-950) 8%, var(--background)); --input: color-mix(in srgb, var(--color-neutral-950) 10%, var(--background)); --ring: var(--color-neutral-400); --sidebar: var(--color-neutral-50); --sidebar-foreground: color-mix( in srgb, var(--color-neutral-800) 64%, var(--sidebar) ); --sidebar-primary: var(--color-neutral-800); --sidebar-primary-foreground: var(--color-neutral-50); --sidebar-accent: color-mix( in srgb, var(--color-neutral-950) 4%, var(--sidebar) ); --sidebar-accent-foreground: var(--color-neutral-800); --sidebar-border: color-mix( in srgb, var(--color-neutral-950) 6%, var(--sidebar) ); --sidebar-ring: var(--color-neutral-400); --chart-1: var(--color-orange-600); --chart-2: var(--color-teal-600); --chart-3: var(--color-cyan-900); --chart-4: var(--color-amber-400); --chart-5: var(--color-amber-500); } .dark { --background: color-mix( in srgb, var(--color-neutral-950) 100%, var(--color-neutral-50) ); --foreground: var(--color-neutral-100); --card: color-mix(in srgb, var(--background) 98%, var(--color-neutral-50)); --card-foreground: var(--color-neutral-100); --popover: color-mix( in srgb, var(--background) 100%, var(--color-neutral-50) ); --popover-foreground: var(--color-neutral-100); --primary: var(--color-neutral-100); --primary-foreground: var(--color-neutral-800); --secondary: color-mix(in srgb, var(--color-white) 4%, var(--background)); --secondary-foreground: var(--color-neutral-100); --muted: color-mix(in srgb, var(--color-neutral-50) 4%, var(--background)); --muted-foreground: color-mix( in srgb, var(--color-neutral-500) 90%, var(--color-neutral-50) ); --accent: color-mix(in srgb, var(--color-neutral-50) 4%, var(--background)); --accent-foreground: var(--color-neutral-100); --destructive: color-mix( in srgb, var(--color-red-600) 90%, var(--color-neutral-50) ); --destructive-foreground: var(--color-red-400); --info: var(--color-blue-500); --info-foreground: var(--color-blue-400); --success: var(--color-emerald-500); --success-foreground: var(--color-emerald-400); --warning: var(--color-amber-500); --warning-foreground: var(--color-amber-400); --border: color-mix(in srgb, var(--color-neutral-50) 6%, var(--background)); --input: color-mix(in srgb, var(--color-neutral-50) 8%, var(--background)); --ring: var(--color-neutral-500); --sidebar: color-mix( in srgb, var(--color-neutral-950) 97%, var(--color-neutral-50) ); --sidebar-foreground: color-mix( in srgb, var(--color-neutral-100) 64%, var(--sidebar) ); --sidebar-primary: var(--color-neutral-100); --sidebar-primary-foreground: var(--color-neutral-800); --sidebar-accent: color-mix( in srgb, var(--color-neutral-50) 4%, var(--sidebar) ); --sidebar-accent-foreground: var(--color-neutral-100); --sidebar-border: color-mix( in srgb, var(--color-neutral-50) 5%, var(--sidebar) ); --sidebar-ring: var(--color-neutral-400); --chart-1: var(--color-blue-700); --chart-2: var(--color-emerald-500); --chart-3: var(--color-amber-500); --chart-4: var(--color-purple-500); --chart-5: var(--color-rose-500); } ``` Font Variables [#font-variables] The theme preset defines `--font-sans` with system fallbacks. To use custom typefaces, set this variable in your layout via `next/font` or `@fontsource-variable`: ```tsx import { Inter } from "next/font/google"; const fontSans = Inter({ variable: "--font-sans", subsets: ["latin"] }); export default function RootLayout({ children }) { return ( {children} ); } ``` Apply the variables on `html` or `body` so they cascade. Customizing Colors [#customizing-colors] To customize colors interactively, use the [Themes](/themes) page: pick primary and gray bases, adjust border radius, preview light and dark, then copy the generated CSS variables into your `globals.css` (or merge them with the snippet below). # January 2026 - Beta 1 (/docs/changelog/26-01-first-beta) Beta 1 marks the first public release of Shark UI. The goal was to build a set of components on [Ark UI](https://ark-ui.com) styled with [Tailwind CSS](https://tailwindcss.com), so you can add accessible primitives to a project without wiring up styles from scratch. Components [#components] Roughly 80+ components are available, grouped into several categories: * **UI primitives**: Accordion, Alert, Avatar, Badge, Button, Card, Separator, Skeleton, Tabs * **Overlays and dialogs**: Dialog, Menu, Popover, Sheet, Tooltip, Hover Card, Context Menu * **Forms and inputs**: Input, Textarea, Checkbox, Radio Group, Select, Native Select, Field, Combobox, Input OTP, File Upload, Editable * **Layout and navigation**: Sidebar, Scroll Area, Resizable, Pagination, Breadcrumb, Bottom Navigation * **Feedback and display**: Progress, Slider, Rating Group, Toggle, Toggle Group, Steps, Carousel Components are copy-paste: they land in your project, and you own the code. Tailwind and Ark UI are the only runtime dependencies. Theme editor [#theme-editor] The theme editor lets you tweak colors and copy the resulting CSS variables into your project. Themes follow the shadcn color convention, with extra variables for success, warning, info, and destructive states. Limitations [#limitations] This is a beta. Some components may change before a stable release. If you run into issues, open an issue on [GitHub](https://github.com/vinihvc/shark-ui). # March 2026 - Release Candidate (/docs/changelog/26-03-rc) This release candidate builds on Beta 1 with new components, utilities, theme editor enhancements, and improved documentation. The component registry has been restructured with thumbs and consistent example naming. New Components [#new-components] * **Announcement** — Banner-style component for promotions and notices * **Calendar** — Full calendar with month/year navigation and range selection * **Color Picker** — Color selection with swatches and input fields * **Date Picker** — Date input with calendar popover and presets * **Drawer** — Slide-out panel for mobile-first layouts * **Image Cropper** — Crop images with zoom, aspect ratio, and constraints * **Prose** — Typography styles for markdown and long-form content * **Signature Pad** — Canvas-based signature capture * **Skip Nav** — Accessibility link to skip to main content * **Timer** — Countdown, interval, and Pomodoro timers New Utilities [#new-utilities] * **Client Only** — Render components only on the client * **Format** — Date, number, and relative time formatting * **Show** — Conditional rendering with animation support * **Download Trigger** — Programmatic file downloads * **Swap** — Toggle between two states with animation * **Presence** — Mount/unmount with enter/exit animations * **JSON Tree View** — Expandable JSON display * **Highlight** — Syntax highlighting for code blocks Component Updates [#component-updates] Registry examples and thumbs were standardized across components: * **Button** — Variants, sizes, and icon support * **Card** — Composition and theming * **Combobox** — Multi-select and controlled state * **Dialog** — Sizes and compositions * **Field** — Required fields and validation * **Floating Panel** — Positions and resize behavior * **Input Group** — Button integration and addons * **Menu** — Nested menus and keyboard navigation * **Number Input** — Stepper and format options * **Pagination** — Sizes and variants * **Select** — Multi-select and custom items * **Textarea** — Autoresize and validation * **Toast** — Placement, types, and promises * **Toggle** — Sizes, outline, and icon variants * **Tooltip** — Positioning and keyboard support * **Tour** — Step types, progress, and async steps Theme Editor [#theme-editor] * Updated color variable system and theme presets * Support for success, warning, info, and destructive states * Prose typography styles for content blocks Home Page [#home-page] * Redesigned hero with Announcement component * Component examples (Input OTP, forms, calendar, charts, and more) * Framework support badges (React, Solid, Vue, Svelte) * Theme cards for activity, chat, and sharing examples Documentation [#documentation] * **Forms** — New forms section with React Hook Form and TanStack Form guides * **RTL** — Right-to-left layout support * Component docs updated with API references and examples * New installation guides for various frameworks Infrastructure [#infrastructure] * Open Graph image generation for docs and changelog * RSS feed and sitemap generation * LLMs.txt route for AI crawlers Dependencies [#dependencies] * Package updates for Next.js, React, Ark UI, and dev tooling # Changelog (/docs/changelog) Releases are listed by date, newest first. Each entry describes what changed—components added or updated, breaking changes, and fixes. For release tags and diffs, check the [Shark UI repository](https://github.com/vinihvc/shark-ui) on GitHub. # React Hook Form (/docs/forms/react-hook-form) This guide shows how to pair [React Hook Form](https://react-hook-form.com) with Shark UI’s form primitives. Shark UI’s `Field` components are built on [Ark UI](https://ark-ui.com), so they work naturally with React Hook Form while keeping validation, error handling, and accessibility in sync. For additional patterns (including native controls with `register` and custom components with `Controller`). Demo [#demo] The example below is a bug report form: a text input for the title and a textarea for the description. On submit, the form is validated with Zod and any errors are shown alongside the relevant fields. > **Note:** Browser validation is disabled in this demo to illustrate schema validation. In production, keep native validation enabled when appropriate. ```tsx "use client" import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Controller, useForm } from "react-hook-form" import * as z from "zod" import { Button } from "@/registry/react/components/button" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/registry/react/components/card" import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel, } from "@/registry/react/components/field" import { Input } from "@/registry/react/components/input" import { InputGroup, InputGroupAddon, InputGroupText, InputGroupTextarea, } from "@/registry/react/components/input-group" const formSchema = z.object({ title: z .string() .min(5, "Bug title must be at least 5 characters.") .max(32, "Bug title must be at most 32 characters."), description: z .string() .min(20, "Description must be at least 20 characters.") .max(100, "Description must be at most 100 characters."), }) export const BugReportForm = () => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { title: "", description: "", }, }) function onSubmit(data: z.infer) { console.log(data) } return ( Bug Report Help us improve by reporting bugs you encounter.
( Bug Title {fieldState.invalid && ( {fieldState.error?.message} )} )} /> ( Description {field.value.length}/100 characters Include steps to reproduce, expected behavior, and what actually happened. {fieldState.invalid && ( {fieldState.error?.message} )} )} />
) } ``` Approach [#approach] The pattern uses React Hook Form for state and Zod for validation. Shark UI’s Ark-based `Field` primitives handle layout and a11y, leaving you full control over markup and styling. * **Form state**: React Hook Form’s `useForm` and `Controller` for controlled fields * **Layout & semantics**: Shark UI `Field`, `FieldLabel`, `FieldError`, and related components * **Validation**: Zod schema with `zodResolver` for client-side validation Anatomy [#anatomy] Typical structure: wrap each field with `Controller`, and use Shark UI’s `Field` for layout and error wiring. ```tsx ( Bug Title Provide a concise title for your bug report. {fieldState.invalid && ( {fieldState.error?.message} )} )} /> ``` Form [#form] Create a form schema [#create-a-form-schema] Define your form shape with a Zod schema. > **Note:** React Hook Form supports other [Standard Schema](https://react-hook-form.com/docs/useform/resolvers#schema-validation) libraries; Zod is used here for clarity. ```tsx import * as z from "zod" const formSchema = z.object({ title: z .string() .min(5, "Bug title must be at least 5 characters.") .max(32, "Bug title must be at most 32 characters."), description: z .string() .min(20, "Description must be at least 20 characters.") .max(100, "Description must be at most 100 characters."), }) ``` Setup the form [#setup-the-form] Create the form with `useForm` and pass `zodResolver(formSchema)` so Zod runs validation. ```tsx import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" const formSchema = z.object({ title: z .string() .min(5, "Bug title must be at least 5 characters.") .max(32, "Bug title must be at most 32 characters."), description: z .string() .min(20, "Description must be at least 20 characters.") .max(100, "Description must be at most 100 characters."), }) export const BugReportForm = () => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { title: "", description: "", }, }) function onSubmit(data: z.infer) { console.log(data) } return (
{/* Build the form here */}
) } ``` Done [#done] With that, the form is ready: accessible labels and errors come from the `Field` components, and Zod runs on submit. Invalid submissions populate `fieldState.error` so `FieldError` can render messages. Validation [#validation] Client-side validation [#client-side-validation] Use a Zod schema as the `resolver` in `useForm`. Validation runs according to the `mode` you set. ```tsx import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" const formSchema = z.object({ title: z.string(), description: z.string().optional(), }) export const ExampleForm = () => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { title: "", description: "", }, }) } ``` Validation modes [#validation-modes] ```tsx const form = useForm>({ resolver: zodResolver(formSchema), mode: "onChange", }) ``` | Mode | Description | | ------------- | -------------------------------------------------------- | | `"onChange"` | Validation triggers on every change. | | `"onBlur"` | Validation triggers on blur. | | `"onSubmit"` | Validation triggers on submit (default). | | `"onTouched"` | Validation triggers on first blur, then on every change. | | `"all"` | Validation triggers on blur and change. | Displaying errors [#displaying-errors] Use `FieldError` and pass `invalid` down so both the `Field` wrapper and the control (e.g. `Input`, `SelectTrigger`, `Checkbox`) get the correct states for styling and ARIA. ```tsx ( Email {fieldState.invalid && ( {fieldState.error?.message} )} )} /> ``` Working with different field types [#working-with-different-field-types] Simple inputs with register [#simple-inputs-with-register] For plain text inputs and textareas, you can use `register` instead of `Controller`—fewer wrappers and less boilerplate. ```tsx const { register, handleSubmit, formState: { errors } } = useForm()
Email {errors.email?.message}
``` Input with `Controller` [#input-with-controller] Use `Controller` when you need controlled behavior (e.g. character counters) or when integrating custom components. Spread `field` onto `Input` and pass `invalid={fieldState.invalid}` to both `Field` and `Input`. ```tsx ( Name {fieldState.invalid && ( {fieldState.error?.message} )} )} /> ``` Textarea [#textarea] Same idea as Input: spread `field` onto `Textarea` or `InputGroupTextarea`, and pass `invalid` to both the `Field` and the textarea. ```tsx ( More about you