# Introduction (/docs/introduction)
---
title: Introduction
description: A set of perfect-designed components built on top of React Aria and Motion.
---
**Kanpeki** is a collection of beautifully designed, accessible, and composable components for your React apps. Built on top of [React Aria Components](https://react-aria.adobe.com/) and styled with [Tailwind CSS](https://tailwindcss.com/), it's designed for you to copy, paste, and own.
We think React Aria Components is the best foundation for modern web applications. We've taken its powerful, accessible primitives and given them a design system that's ready to go, right out of the box.
## How It Works
Our approach is simple: you should own your code. We're inspired by the copy-paste ethos of **shadcn/ui**.
Instead of installing a package, you get the source code. This means:
- **No abstractions:** You have full control over how components look and behave.
- **Endless customization:** Need to change something? Just edit the file.
- **Learn by doing:** See how components are built and adapt them to your needs.
**What do you mean by not a component library?**
You do not install it as a dependency. It is not available or distributed via npm. Pick the components you need. Copy and paste the code into your project and customize to your needs. The code is yours.
## Like shadcn/ui, but with React Aria
If you've used shadcn/ui, you'll feel right at home. The main difference is the foundation: **shadcn/ui uses Radix UI**, while **Kanpeki uses React Aria Components**.
We chose React Aria Components for its robust, accessible, and unopinionated primitives. It handles the hard parts—like accessibility, keyboard navigation, and focus management—without imposing any styles. This gives us the freedom to create a design system that is both beautiful and flexible.
## Modern Tooling
Kanpeki uses modern tools that improve developer experience:
### CVA Beta (Class Variance Authority)
Instead of the old `class-variance-authority` package, we use the new beta version simply called [`cva`](https://beta.cva.style/). This new version addresses one of the biggest complaints: the name was just too long.
**Key improvements:**
- **Simpler configuration:** No more `cn` utility hack. Configure `tailwind-merge` directly:
```ts title="lib/cva.ts"
import { defineConfig } from "cva";
import { twMerge } from "tailwind-merge";
export const {
cva,
cx: cn,
compose,
} = defineConfig({
hooks: {
onComplete: (className) => twMerge(className),
},
});
```
Compare this to the old shadcn approach:
```ts title="lib/utils.ts"
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
- **New features:**
- [`compose`](https://beta.cva.style/getting-started/composing-components) - Shallow merge multiple `cva` components
- [`defineConfig`](https://beta.cva.style/api-reference#defineconfig) - Generate `cva`, `cx` and `compose` functions based on your preferred configuration
- [Cleaner API with single parameter (base styles via `base` property)](https://beta.cva.style/getting-started/whats-new#2-cva-now-accepts-a-single-parameter)
- [Better TypeScript support](https://beta.cva.style/getting-started/whats-new#4-clearer-type-guards)
This is why Kanpeki doesn't need `class-variance-authority` or `clsx`.
### Tailwind CSS Motion
Instead of `tw-animate-css`, we use [`tailwindcss-motion`](https://docs.rombo.co/tailwind) for animations. It provides:
- More animation presets out of the box
- Better customization options
- More flexible animation controls
- Modern animation utilities
## Open Source
This project is open source and built in public. We welcome contributions, feedback, and improvements.
## Inspirations
Here are some cool projects that inspired Kanpeki:
- [shadcn/ui](https://ui.shadcn.com)
- [coss ui](https://coss.com/ui/docs)
- [Intent UI](https://intentui.com)
- [Radix Themes](https://www.radix-ui.com)
- [dotUI](https://dotui.org)
- [JollyUI](https://www.jollyui.dev)
- [Magic UI](https://magicui.design)
- [Aceternity UI](https://ui.aceternity.com)
- [Luxe](https://www.luxeui.com)
# Typography (/docs/typography)
---
title: Typography
description: Styles for headings, paragraphs, lists...etc
---
## Component Preview: typography-demo
```tsx
export function TypographyDemo() {
return (
The Joke Tax Chronicles
Once upon a time, in a far-off land, there was a very lazy king who
spent all day lounging on his throne. One day, his advisors came to him
with a problem: the kingdom was running out of money.
The King's Plan
The king thought long and hard, and finally came up with{" "}
a brilliant plan
: he would tax the jokes in the kingdom.
"After all," he said, "everyone enjoys a good joke, so it's only fair
that they should pay for the privilege."
The Joke Tax
The king's subjects were not amused. They grumbled and complained, but
the king was firm:
1st level of puns: 5 gold coins
2nd level of jokes: 10 gold coins
3rd level of one-liners : 20 gold coins
As a result, people stopped telling jokes, and the kingdom fell into a
gloom. But there was one person who refused to let the king's
foolishness get him down: a court jester named Jokester.
Jokester's Revolt
Jokester began sneaking into the castle in the middle of the night and
leaving jokes all over the place: under the king's pillow, in his soup,
even in the royal toilet. The king was furious, but he couldn't seem to
stop Jokester.
And then, one day, the people of the kingdom discovered that the jokes
left by Jokester were so funny that they couldn't help but laugh. And
once they started laughing, they couldn't stop.
The People's Rebellion
The people of the kingdom, feeling uplifted by the laughter, started to
tell jokes and puns again, and soon the entire kingdom was in on the
joke.
King's Treasury
People's happiness
Empty
Overflowing
Modest
Satisfied
Full
Ecstatic
The king, seeing how much happier his subjects were, realized the error
of his ways and repealed the joke tax. Jokester was declared a hero, and
the kingdom lived happily ever after.
The moral of the story is: never underestimate the power of a good laugh
and always be careful of bad ideas.
```
## Examples
## Component Preview: card-demo
```tsx
import { Button } from "~/components/ui/button/button";
import { Card } from "~/components/ui/card";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label/label";
export function CardDemo() {
return (
Login to your account
Enter your email below to login to your account
);
}
```
# Carousel (/docs/components/carousel)
---
title: Carousel
description: A carousel with motion and swipe built using Embla.
links:
docs: https://www.embla-carousel.com/get-started/react
api: https://www.embla-carousel.com/api
---
## Component Preview: carousel-demo
```tsx
import { Card } from "~/components/ui/card";
import { Carousel } from "~/components/ui/carousel";
export function CarouselDemo() {
return (
{Array.from({ length: 5 }).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: This is fine
{index + 1}
))}
);
}
```
## About
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/carousel
```
Install the following dependencies:
```bash
npm install embla-carousel-react
```
Copy and paste the following code into your project.
## Component Source: carousel
```tsx
// File: index.ts
export * from "./carousel";
export * as Carousel from "./namespace";
// File: carousel.tsx
"use client";
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react";
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import { createContext, use, useCallback, useEffect, useState } from "react";
import { cn } from "~/lib/cva";
import { Button, type ButtonProps } from "~/components/ui/button/button";
export type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
export interface CarouselProps extends React.ComponentProps<"div"> {
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void;
}
interface CarouselContextProps extends CarouselProps {
carouselRef: ReturnType[0];
api: ReturnType[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
}
const CarouselContext = createContext(null);
function useCarousel() {
const context = use(CarouselContext);
if (!context) {
throw new Error("useCarousel must be used within a ");
}
return context;
}
export function CarouselRoot({
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
}: CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
);
const [canScrollPrev, setCanScrollPrev] = useState(false);
const [canScrollNext, setCanScrollNext] = useState(false);
const onSelect = useCallback((api: CarouselApi) => {
if (!api) {
return;
}
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
const scrollPrev = useCallback(() => {
api?.scrollPrev();
}, [api]);
const scrollNext = useCallback(() => {
api?.scrollNext();
}, [api]);
const handleKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
scrollPrev();
} else if (event.key === "ArrowRight") {
event.preventDefault();
scrollNext();
}
},
[scrollPrev, scrollNext]
);
useEffect(() => {
if (!(api && setApi)) {
return;
}
setApi(api);
}, [api, setApi]);
useEffect(() => {
if (!api) {
return;
}
onSelect(api);
api.on("reInit", onSelect);
api.on("select", onSelect);
return () => {
api?.off("select", onSelect);
};
}, [api, onSelect]);
return (
{/** biome-ignore lint/a11y/useAriaPropsSupportedByRole: The role is correct. */}
{children}
);
}
export interface CarouselContentProps extends React.ComponentProps<"div"> {}
export function CarouselContent({ className, ...props }: CarouselContentProps) {
const { carouselRef, orientation } = useCarousel();
return (
);
}
export interface CarouselItemProps extends React.ComponentProps<"div"> {}
export function CarouselItem({ className, ...props }: CarouselItemProps) {
const { orientation } = useCarousel();
return (
);
}
export interface CarouselPreviousProps extends ButtonProps {}
export function CarouselPrevious({
className,
variant = "outline",
size = "icon",
...props
}: CarouselPreviousProps) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
);
}
export interface CarouselNextProps extends ButtonProps {}
export function CarouselNext({
className,
variant = "outline",
size = "icon",
...props
}: CarouselNextProps) {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
);
}
// File: namespace.ts
export {
CarouselContent as Content,
CarouselItem as Item,
CarouselNext as Next,
CarouselPrevious as Previous,
CarouselRoot as Root,
} from "./carousel";
```
Update the import paths to match your project setup.
## Anatomy
```tsx
import { Carousel } from "~/components/ui/carousel";
.........;
```
## Examples
### Sizes
To set the size of the items, you can use the `basis{:tsx}` utility class on the `{:tsx}`.
## Component Preview: carousel-size-demo
```tsx
import { Card } from "~/components/ui/card";
import { Carousel } from "~/components/ui/carousel";
export function CarouselSizeDemo() {
return (
{Array.from({ length: 5 }).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: This is fine
{index + 1}
))}
);
}
```
```tsx
// [!code word:basis-1/3]
// 33% of the carousel width.
.........
```
```tsx
// [!code word:md\:basis-1/2]
// [!code word:lg\:basis-1/3]
// 50% on small screens and 33% on larger screens.
.........
```
### Spacing
To set the spacing between the items, we use a `pl-[VALUE]{:tsx}` utility on the `{:tsx}` and a negative `-ml-[VALUE]{:tsx}` on the `{:tsx}`.
**Why:** I tried to use the `gap{:tsx}` property or a `grid{:tsx}` layout on the `{:tsx}` but it required a lot of math and mental effort to get the spacing right. I found `pl-[VALUE]{:tsx}` and `-ml-[VALUE]{:tsx}` utilities much easier to use.
You can always adjust this in your own project if you need to.
## Component Preview: carousel-spacing-demo
```tsx
import { Card } from "~/components/ui/card";
import { Carousel } from "~/components/ui/carousel";
export function CarouselSpacingDemo() {
return (
{Array.from({ length: 5 }).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: This is fine
{index + 1}
))}
);
}
```
```tsx
// [!code word:-ml-4]
// [!code word:pl-4]
.........
```
```tsx
// [!code word:-ml-2]
// [!code word:md\:-ml-4]
// [!code word:pl-2]
// [!code word:md\:pl-4]
.........
```
### Orientation
Use the `orientation{:tsx}` prop to set the orientation of the carousel.
## Component Preview: carousel-orientation-demo
```tsx
import { Card } from "~/components/ui/card";
import { Carousel } from "~/components/ui/carousel";
export function CarouselOrientationDemo() {
return (
{Array.from({ length: 5 }).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: This is fine
{index + 1}
))}
);
}
```
```tsx
// [!code word:orientation]
.........
```
## Options
You can pass options to the carousel using the `opts{:tsx}` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information.
```tsx
.........
```
## API
Use a state and the `setApi{:tsx}` prop to get an instance of the carousel API.
## Component Preview: carousel-api-demo
```tsx
"use client";
import { useEffect, useState } from "react";
import { Card } from "~/components/ui/card";
import { Carousel, type CarouselApi } from "~/components/ui/carousel";
export function CarouselApiDemo() {
const [api, setApi] = useState();
const [current, setCurrent] = useState(0);
const [count, setCount] = useState(0);
useEffect(() => {
if (!api) {
return;
}
setCount(api.scrollSnapList().length);
setCurrent(api.selectedScrollSnap() + 1);
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1);
});
}, [api]);
return (
{Array.from({ length: 5 }).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: This is fine
{index + 1}
))}
Slide {current} of {count}
);
}
```
```tsx
import { useEffect, useState } from "react";
import { Carousel, type CarouselApi } from "~/registry/ui/carousel"; // [!code highlight]
export function Example() {
const [api, setApi] = useState(); // [!code highlight]
const [current, setCurrent] = useState(0);
const [count, setCount] = useState(0);
useEffect(() => {
if (!api) {
return;
}
setCount(api.scrollSnapList().length);
setCurrent(api.selectedScrollSnap() + 1);
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1);
});
}, [api]);
return (
// [!code highlight]
.........
);
}
```
## Events
You can listen to events using the api instance from `setApi{:tsx}`.
```tsx
import type { CarouselApi } from "~/components/ui/carousel"; // [!code highlight]
export function Example() {
// [!code highlight:11]
const [api, setApi] = useState();
useEffect(() => {
if (!api) {
return;
}
api.on("select", () => {
// Do something on select.
});
}, [api]);
return (
// [!code highlight]
.........
);
}
```
See the [Embla Carousel docs](https://www.embla-carousel.com/api/events/) for more information on using events.
## Plugins
You can use the `plugins{:tsx}` prop to add plugins to the carousel.
```tsx
import Autoplay from "embla-carousel-autoplay"; // [!code highlight]
export function Example() {
return (
// ...
);
}
```
## Component Preview: carousel-plugin-demo
```tsx
"use client";
import Autoplay from "embla-carousel-autoplay";
import { useRef } from "react";
import { Card } from "~/components/ui/card";
import { Carousel } from "~/components/ui/carousel";
export function CarouselPluginDemo() {
const plugin = useRef(Autoplay({ delay: 2000 }));
return (
{Array.from({ length: 5 }).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: This is fine
{index + 1}
))}
);
}
```
See the [Embla Carousel docs](https://www.embla-carousel.com/api/plugins/) for more information on using plugins.
# Checkbox (/docs/components/checkbox)
---
title: Checkbox
description: A control that allows the user to toggle between checked and not checked.
links:
docs: https://react-aria.adobe.com/Checkbox
---
## Component Preview: checkbox-demo
```tsx
import { Checkbox } from "~/components/ui/checkbox";
export function CheckboxDemo() {
return (
Accept terms and conditions
);
}
```
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/checkbox
```
Copy and paste the following code into your project.
## Component Source: checkbox
```tsx
// File: index.ts
export * from "./checkbox";
export * as Checkbox from "./namespace";
export * from "./styles";
// File: checkbox.tsx
"use client";
import { CheckIcon, MinusIcon } from "lucide-react";
import { Checkbox, composeRenderProps } from "react-aria-components";
import { CheckboxStyles } from "./styles";
export interface CheckboxProviderProps
extends React.ComponentProps {}
export function CheckboxProvider({
className,
...props
}: CheckboxProviderProps) {
return (
CheckboxStyles.Provider({ className })
)}
data-slot="checkbox-provider"
/>
);
}
export interface CheckboxRootProps extends React.ComponentProps<"div"> {}
export function CheckboxRoot({ className, ...props }: CheckboxRootProps) {
return (
);
}
export interface CheckboxIndicatorProps extends React.ComponentProps<"svg"> {}
export function CheckboxIndicator({
className,
...props
}: CheckboxIndicatorProps) {
return (
<>
>
);
}
// File: styles.ts
import { cva } from "~/lib/cva";
export const CheckboxStyles = {
Indicator: cva({
base: ["hidden size-3"],
}),
Provider: cva({
base: [
"group flex items-center gap-3 font-medium text-sm leading-none transition",
"disabled:opacity-50",
],
}),
Root: cva({
base: [
"grid size-4 shrink-0 place-content-center rounded-sm border border-toggle bg-secondary text-background transition",
"group-selected:border-primary/70 group-selected:bg-primary group-selected:text-primary-foreground",
"group-selected:group-invalid:border-destructive/70 group-selected:group-invalid:bg-destructive group-selected:group-invalid:text-destructive-foreground",
"group-focus-visible:border-primary/70 group-focus-visible:ring-4 group-focus-visible:ring-primary/20",
"group-focus-visible:group-invalid:border-destructive/70 group-focus-visible:group-invalid:text-destructive-foreground group-focus-visible:group-invalid:ring-destructive/20",
"group-invalid:border-destructive/70 group-invalid:bg-destructive/20 group-invalid:text-destructive-foreground group-invalid:ring-destructive/20",
"group-disabled:cursor-not-allowed",
],
}),
};
// File: namespace.ts
export {
CheckboxIndicator as Indicator,
CheckboxProvider as Provider,
CheckboxRoot as Root,
} from "./checkbox";
```
Update the import paths to match your project setup.
## Anatomy
Single importMultiple imports
```tsx
import { Checkbox } from "~/components/ui/checkbox";
Label
;
```
```tsx
import {
CheckboxIndicator,
CheckboxProvider,
CheckboxRoot,
} from "~/components/ui/checkbox";
Label
;
```
## Examples
### Disabled
## Component Preview: checkbox-disabled-demo
```tsx
import { Checkbox } from "~/components/ui/checkbox";
export function CheckboxDisabledDemo() {
return (
Enable notifications
);
}
```
### Group
## Component Preview: checkbox-group-demo
```tsx
import { Checkbox } from "~/components/ui/checkbox";
export function CheckboxGroupDemo() {
return (
Enable notifications
You can enable or disable notifications at any time.
);
}
```
### Form
Under Construction
This component is currently being built. Check back soon for updates!
# Collapsible (/docs/components/collapsible)
---
title: Collapsible
description: An interactive component which expands/collapses a panel.
links:
docs: https://react-aria.adobe.com/Disclosure
---
## Component Preview: collapsible-demo
```tsx
import { ChevronsUpDownIcon } from "lucide-react";
import { Button } from "~/components/ui/button";
import { Collapsible } from "~/components/ui/collapsible";
export function CollapsibleDemo() {
return (
@peduarte starred 3 repositories
@radix-ui/primitives
@radix-ui/colors
@stitches/react
);
}
```
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/collapsible
```
Copy and paste the following code into your project.
## Component Source: collapsible
```tsx
// File: index.ts
export * from "./collapsible";
export * as Collapsible from "./namespace";
export * from "./styles";
// File: collapsible.tsx
"use client";
import {
Button,
composeRenderProps,
Disclosure,
DisclosurePanel,
} from "react-aria-components";
import { CollapsibleStyles } from "./styles";
export interface CollapsibleRootProps
extends React.ComponentProps {}
export function CollapsibleRoot(props: CollapsibleRootProps) {
return ;
}
export interface CollapsibleTriggerProps
extends Omit, "slot"> {}
export function CollapsibleTrigger(props: CollapsibleTriggerProps) {
return ;
}
export interface CollapsibleContentProps
extends React.ComponentProps {}
export function CollapsibleContent({
className,
...props
}: CollapsibleContentProps) {
return (
CollapsibleStyles.Content({ className })
)}
data-slot="collapsible-content"
{...props}
/>
);
}
// File: styles.ts
import { cva } from "~/lib/cva";
export const CollapsibleStyles = {
Content: cva({
base: [
"h-(--disclosure-panel-height) transform-gpu overflow-clip text-sm duration-300 motion-safe:transition-[height]",
],
}),
};
// File: namespace.ts
export {
CollapsibleContent as Content,
CollapsibleRoot as Root,
CollapsibleTrigger as Trigger,
} from "./collapsible";
```
Update the import paths to match your project setup.
## Anatomy
Single importMultiple imports
```tsx
import { Collapsible } from "~/components/ui/collapsible";
Is it accessible?
Yes. It adheres to the WAI-ARIA design pattern.
;
```
```tsx
import {
CollapsibleContent,
CollapsibleRoot,
CollapsibleTrigger,
} from "~/components/ui/collapsible";
Is it accessible?
Yes. It adheres to the WAI-ARIA design pattern.
;
```
## Trigger
You can use the `{:tsx}` component to create a button that toggles the visibility of the collapsible content,
or you can use the `{:tsx}` component as the trigger by passing it a `slot="trigger"{:tsx}` prop.
```tsx
{/* [!code highlight] */}
Yes. It adheres to the WAI-ARIA design pattern.
```
## Animation
The accordion uses the `--disclosure-panel-height{:css}` CSS variable to animate the height of the content area.
# Date Field (/docs/components/date-field)
---
title: Date Field
description: A date field allows users to enter and edit date and time values using a keyboard. Each part of a date value is displayed in an individually editable segment.
links:
docs: https://react-aria.adobe.com/DateField
---
## Component Preview: date-field-demo
```tsx
"use client";
import { getLocalTimeZone, today } from "@internationalized/date";
import { DateField } from "~/components/ui/date-field";
import { InputStyles } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
export function DateFieldDemo() {
return (
{(segment) => }
);
}
```
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/date-field
```
Copy and paste the following code into your project.
## Component Source: date-field
```tsx
// File: index.ts
export * from "./date-field";
export * as DateField from "./namespace";
export * from "./styles";
// File: date-field.tsx
"use client";
import {
composeRenderProps,
DateInput,
DateSegment,
type DateValue,
DateField as RACDateField,
} from "react-aria-components";
import { cn } from "~/lib/cva";
import { DateFieldStyles } from "./styles";
export interface DateFieldRootProps
extends React.ComponentProps> {}
export function DateFieldRoot({
className,
...props
}: DateFieldRootProps) {
return (
cn("group grid gap-3", className)
)}
data-slot="date-field-root"
/>
);
}
export interface DateFieldInputProps
extends React.ComponentProps {}
export function DateFieldInput(props: DateFieldInputProps) {
return ;
}
export interface DateFieldSegmentProps
extends React.ComponentProps {}
export function DateFieldSegment({
className,
...props
}: DateFieldSegmentProps) {
return (
DateFieldStyles.Segment({ className })
)}
data-slot="date-field-segment"
/>
);
}
// File: styles.ts
import { cva } from "~/lib/cva";
export const DateFieldStyles = {
Segment: cva({
base: [
"rounded-sm p-0.5 outline-none transition",
"data-placeholder:text-muted-foreground",
"type-literal:px-0 type-literal:text-muted-foreground",
"focus:bg-ring/50",
],
}),
};
// File: namespace.ts
export {
DateFieldInput as Input,
DateFieldRoot as Root,
DateFieldSegment as Segment,
} from "./date-field";
```
Update the import paths to match your project setup.
## Anatomy
Single importMultiple imports
```tsx
import { DateField } from "~/components/ui/date-field";
{(segment) => }
;
```
```tsx
import {
DateFieldInput,
DateFieldRoot,
DateFieldSegment,
} from "~/components/ui/date-field";
{(segment) => }
;
```
## Examples
### Disabled
## Component Preview: date-field-disabled-demo
```tsx
"use client";
import { getLocalTimeZone, today } from "@internationalized/date";
import { DateField } from "~/components/ui/date-field";
import { InputStyles } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
export function DateFieldDisabledDemo() {
return (
{(segment) => }
);
}
```
### Timezone
Under Construction
This component is currently being built. Check back soon for updates!
### Granularity
Under Construction
This component is currently being built. Check back soon for updates!
### Form
Under Construction
This component is currently being built. Check back soon for updates!
# Dialog (/docs/components/dialog)
---
title: Dialog
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
links:
docs: https://react-aria.adobe.com/Modal
---
## Component Preview: dialog-demo
```tsx
import { Button } from "~/components/ui/button";
import { Dialog } from "~/components/ui/dialog";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { TextField } from "~/components/ui/text-field";
export function DialogDemo() {
return (
Edit profile
Make changes to your profile here. Click save when you're
done.
);
}
```
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/dialog
```
Copy and paste the following code into your project.
## Component Source: dialog
```tsx
// File: index.ts
export * from "./dialog";
export * as Dialog from "./namespace";
export * from "./styles";
// File: dialog.tsx
"use client";
import type { VariantProps } from "cva";
import { XIcon } from "lucide-react";
import {
Button,
composeRenderProps,
Dialog,
DialogTrigger,
Heading,
Modal,
ModalOverlay,
} from "react-aria-components";
import { DialogStyles } from "./styles";
export interface DialogRootProps
extends React.ComponentProps {}
export function DialogRoot(props: DialogRootProps) {
return ;
}
export interface DialogModalProps
extends React.ComponentProps,
VariantProps {
/**
* Whether to close the modal when the user interacts outside it.
* @default true
*/
isDismissable?: boolean;
}
export function DialogModal({
className,
isDismissable = true,
...props
}: DialogModalProps) {
return (
DialogStyles.Modal({ className })
)}
data-slot="dialog-modal"
isDismissable={isDismissable}
{...props}
/>
);
}
export interface DialogOverlayProps
extends React.ComponentProps,
VariantProps {
/**
* Whether to close the modal when the user interacts outside it.
* @default true
*/
isDismissable?: boolean;
}
export function DialogOverlay({
className,
isBlurred,
isDismissable = true,
...props
}: DialogOverlayProps) {
return (
DialogStyles.Overlay({ className, isBlurred })
)}
data-slot="dialog-overlay"
isDismissable={isDismissable}
{...props}
/>
);
}
export interface DialogContentProps
extends React.ComponentProps {}
export function DialogContent({ className, ...props }: DialogContentProps) {
return (
);
}
export interface DialogCloseProps
extends Omit, "children" | "slot"> {}
export function DialogClose({ className, ...props }: DialogCloseProps) {
return (
);
}
export interface DialogHeaderProps extends React.ComponentProps<"div"> {}
export function DialogHeader({ className, ...props }: DialogHeaderProps) {
return (
);
}
export interface DialogFooterProps extends React.ComponentProps<"div"> {}
export function DialogFooter({ className, ...props }: DialogFooterProps) {
return (
);
}
export interface DialogTitleProps
extends React.ComponentProps {}
export function DialogTitle({ className, ...props }: DialogTitleProps) {
return (
);
}
export interface DialogDescriptionProps extends React.ComponentProps<"p"> {}
export function DialogDescription({
className,
...props
}: DialogDescriptionProps) {
return (
);
}
// File: styles.ts
import { cva } from "~/lib/cva";
export const DialogStyles = {
Close: cva({
base: [
"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity",
"hover:opacity-100",
"focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
"disabled:pointer-events-none",
"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
],
}),
Content: cva({
base: ["grid gap-4 outline-none"],
}),
Description: cva({
base: ["text-muted-foreground text-sm"],
}),
Footer: cva({
base: ["flex flex-col-reverse", "sm:flex-row sm:justify-end sm:space-x-2"],
}),
Header: cva({
base: ["flex flex-col gap-2 text-center sm:text-left"],
}),
Modal: cva({
base: [
"fixed z-50 w-full bg-background p-4 shadow-lg outline-none",
"entering:motion-opacity-in motion-duration-200 exiting:motion-opacity-out motion-ease",
"sm:rounded-lg",
"top-1/2 left-1/2 max-w-lg -translate-x-1/2 -translate-y-1/2 border p-6",
"entering:motion-scale-in-95",
"exiting:motion-scale-out-95",
],
}),
Overlay: cva({
base: [
"fixed inset-0 z-50",
"entering:motion-opacity-in motion-duration-200 motion-ease exiting:motion-opacity-out",
],
defaultVariants: {
isBlurred: false,
},
variants: {
isBlurred: {
false: ["bg-black/15 dark:bg-black/60"],
true: ["backdrop-blur"],
},
},
}),
Title: cva({
base: ["font-semibold text-lg leading-none"],
}),
};
// File: namespace.ts
export {
DialogClose as Close,
DialogContent as Content,
DialogDescription as Description,
DialogFooter as Footer,
DialogHeader as Header,
DialogModal as Modal,
DialogOverlay as Overlay,
DialogRoot as Root,
DialogTitle as Title,
} from "./dialog";
```
Update the import paths to match your project setup.
## Anatomy
Single importMultiple imports
```tsx
import { Button } from "~/components/ui/button";
import { Dialog } from "~/components/ui/dialog";
;
```
```tsx
import { Button } from "~/components/ui/button";
import {
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogModal,
DialogOverlay,
DialogRoot,
DialogTitle,
} from "~/components/ui/dialog";
;
```
## Examples
### Scrollable
## Component Preview: dialog-scrollable-demo
```tsx
import { Button } from "~/components/ui/button";
import { Dialog } from "~/components/ui/dialog";
export function DialogScrollableDemo() {
return (
Scrollable Content
This is a dialog. with scrollable content.
Lorem Ipsum
{Array.from({ length: 10 }).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: This is fine
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
))}
);
}
```
### Alert Dialog
Use the `role="alertdialog"{:tsx}` prop on the `{:tsx}` element to make an alert dialog.
Also, make sure to set the `isDismissable` prop to `false{:ts}` on the `{:tsx}` or `{:tsx}`
to prevent the dialog from being dismissed by clicking outside of it.
## Component Preview: alert-dialog-demo
```tsx
import { Button } from "~/components/ui/button/button";
import { Dialog } from "~/components/ui/dialog";
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.
);
}
```
### Sheet
Use the `side{:tsx}` prop on the `{:tsx}` element to make a sheet dialog.
## Component Preview: dialog-sheet-demo
> ⚠️ Component 'dialog-sheet-demo' not found in registry
## API Reference
### Root
void)",
description:
"Handler that is called when the overlay's open state changes.",
},
]}
/>
### Content
# Field (/docs/components/field)
---
title: Field
description: Combine labels, controls, and help text to compose accessible form fields.
---
## Component Preview: field-demo
```tsx
import { Field } from "~/components/ui/field";
import { Input } from "~/components/ui/input";
import { TextField } from "~/components/ui/text-field";
export function FieldDemo() {
return (
Profile
This information will be displayed on your profile.
}>
Full name
This appears on invoices and emails.
}>
UsernameThis username is already taken.
);
}
```
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/field
```
Copy and paste the following code into your project.
## Component Source: field
```tsx
// File: index.ts
export * from "./field";
export * as Field from "./namespace";
export * from "./styles";
// File: field.tsx
"use client";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import type { VariantProps } from "cva";
import { use, useMemo } from "react";
import {
FieldError as AriaFieldError,
FieldErrorContext,
Text,
} from "react-aria-components";
import { Label } from "~/components/ui/label";
import { Separator } from "~/components/ui/separator";
import { FieldStyles } from "./styles";
export interface FieldSetProps extends React.ComponentProps<"fieldset"> {}
export function FieldSet({ className, ...props }: FieldSetProps) {
return (
);
}
export interface FieldLegendProps
extends React.ComponentProps<"legend">,
VariantProps {}
export function FieldLegend({
className,
variant = "legend",
...props
}: FieldLegendProps) {
return (
);
}
export interface FieldGroupProps extends React.ComponentProps<"div"> {}
export function FieldGroup({ className, ...props }: FieldGroupProps) {
return (
);
}
export interface FieldRootProps
extends useRender.ComponentProps<"div">,
VariantProps {}
export function FieldRoot({
render,
className,
orientation = "vertical",
...props
}: FieldRootProps) {
const defaultProps: useRender.ElementProps<"div"> = {
className: FieldStyles.Root({ orientation, className }),
["data-orientation" as string]: orientation,
["data-slot" as string]: "field-root",
role: "group",
};
return useRender({
defaultTagName: "div",
render,
props: mergeProps<"div">(defaultProps, props),
});
}
export interface FieldContentProps extends React.ComponentProps<"div"> {}
export function FieldContent({ className, ...props }: FieldContentProps) {
return (
);
}
export interface FieldLabelProps extends React.ComponentProps {}
export function FieldLabel({ className, ...props }: FieldLabelProps) {
return (
);
}
export interface FieldTitleProps extends React.ComponentProps<"div"> {}
export function FieldTitle({ className, ...props }: FieldTitleProps) {
return (
);
}
export interface FieldDescriptionProps
extends React.ComponentProps {}
export function FieldDescription({
className,
...props
}: FieldDescriptionProps) {
return (
);
}
export interface FieldSeparatorProps extends React.ComponentProps<"div"> {
children?: React.ReactNode;
}
export function FieldSeparator({
children,
className,
...props
}: FieldSeparatorProps) {
return (
);
}, [children, _errors]);
if (!content) {
return null;
}
return (
{content}
);
}
// File: namespace.ts
export {
FieldContent as Content,
FieldDescription as Description,
FieldError as Error,
FieldGroup as Group,
FieldLabel as Label,
FieldLegend as Legend,
FieldRoot as Root,
FieldSeparator as Separator,
FieldSet as Set,
FieldTitle as Title,
} from "./field";
// File: styles.ts
import { cva } from "~/lib/cva";
export const FieldStyles = {
Set: cva({
base: [
"flex flex-col gap-6",
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
],
}),
Legend: cva({
base: ["mb-3 font-medium"],
variants: {
variant: {
legend: "text-base",
label: "text-sm",
},
},
defaultVariants: {
variant: "legend",
},
}),
Group: cva({
base: [
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
],
}),
Root: cva({
base: "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
variants: {
orientation: {
vertical: ["flex-col *:w-full [&>.sr-only]:w-auto"],
horizontal: [
"flex-row items-center",
"*:data-[slot=field-label]:flex-auto",
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
responsive: [
"@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto [&>.sr-only]:w-auto",
"@md/field-group:*:data-[slot=field-label]:flex-auto",
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
},
},
defaultVariants: {
orientation: "vertical",
},
}),
Content: cva({
base: ["group/field-content flex flex-1 flex-col gap-1.5 leading-snug"],
}),
Label: cva({
base: [
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-4",
"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
],
}),
Title: cva({
base: [
"flex w-fit items-center gap-2 font-medium text-sm leading-snug group-data-[disabled=true]/field:opacity-50",
],
}),
Description: cva({
base: [
"font-normal text-muted-foreground text-sm leading-normal group-has-orientation-horizontal/field:text-balance",
"nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
],
}),
Separator: cva({
base: [
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
],
}),
Error: cva({
base: ["font-normal text-destructive text-sm"],
}),
};
```
Update the import paths to match your project setup.
## Anatomy
Single importMultiple imports
```tsx
import { Field } from "~/components/ui/field";
ProfileLabel
{/* Input, Select, Switch, etc. */}
Optional helper text.Validation message.;
```
```tsx
import {
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldLegend,
FieldRoot,
FieldSet,
} from "~/components/ui/field";
;
```
### Structure
- `{:tsx}` is the core wrapper for a single field.
- `{:tsx}` is a flex column that groups label and description. Not required if you have no description.
- Wrap related fields with `{:tsx}`, and use `{:tsx}` with `{:tsx}` for semantic grouping.
## Form
See the [Forms](/docs/forms) documentation for building forms with the Field component and [TanStack Form](/docs/forms/tanstack-form) or [React Hook Form](/docs/forms/react-hook-form).
## Examples
### Input
Use `{:tsx}` with the `render{:tsx}` prop to compose with `{:tsx}` for automatic accessibility wiring.
## Component Preview: field-input-demo
```tsx
import { Field } from "~/components/ui/field";
import { Input } from "~/components/ui/input";
import { TextField } from "~/components/ui/text-field";
export function FieldInputDemo() {
return (
}>
EmailWe'll never share your email.
);
}
```
### Without TextField
You can use `{:tsx}` with `{:tsx}` directly, but you must provide `htmlFor{:tsx}` on the label and `id{:tsx}` on the input manually.
```tsx
Email
```
Manual ID wiring
Without TextField, you lose React Aria's automatic ID association for label,
description, and error elements. Use `{:tsx}` when accessibility is
critical.
### Textarea
## Component Preview: field-textarea-demo
```tsx
import { Field } from "~/components/ui/field";
import { TextField } from "~/components/ui/text-field";
import { Textarea } from "~/components/ui/textarea";
export function FieldTextareaDemo() {
return (
}>
BioMax 500 characters.
);
}
```
### Select
## Component Preview: field-select-demo
```tsx
import { Field } from "~/components/ui/field";
import { Listbox } from "~/components/ui/list-box";
import { Popover } from "~/components/ui/popover";
import { Select } from "~/components/ui/select";
export function FieldSelectDemo() {
return (
}>
Favorite fruitAppleBananaBlueberryPineappleChoose your favorite fruit.
);
}
```
### Checkbox
## Component Preview: field-checkbox-demo
```tsx
import { Checkbox } from "~/components/ui/checkbox";
import { Field } from "~/components/ui/field";
export function FieldCheckboxDemo() {
return (
}>
Accept terms and conditions
You agree to our Terms of Service and Privacy Policy.
);
}
```
### Radio Group
## Component Preview: field-radio-demo
```tsx
import { Field } from "~/components/ui/field";
import { Label } from "~/components/ui/label";
import { RadioGroup } from "~/components/ui/radio-group";
export function FieldRadioDemo() {
return (
Notification preferences
How would you like to receive notifications?
}
>
}
>
}
>
);
}
```
### Switch
## Component Preview: field-switch-demo
```tsx
import { useId } from "react";
import { Field } from "~/components/ui/field";
import { Switch } from "~/components/ui/switch";
export function FieldSwitchDemo() {
const id = useId();
return (
Marketing emails
Receive emails about new products and features.
);
}
```
### Fieldset
Use `{:tsx}` and `{:tsx}` for semantic grouping of related fields.
## Component Preview: field-fieldset-demo
```tsx
import { Field } from "~/components/ui/field";
import { Input } from "~/components/ui/input";
import { TextField } from "~/components/ui/text-field";
export function FieldFieldsetDemo() {
return (
Shipping Address
We need your address to deliver your order.
}>
Street address
}>
City}>
Postal code
);
}
```
### Field Group
Stack `{:tsx}` components with `{:tsx}`. Add `{:tsx}` to divide them.
## Component Preview: field-group-demo
```tsx
import { Field } from "~/components/ui/field";
import { Input } from "~/components/ui/input";
import { TextField } from "~/components/ui/text-field";
export function FieldGroupDemo() {
return (
}>
EmailOr}>
Phone
);
}
```
## Responsive Layout
- **Vertical fields:** Default orientation stacks label, control, and helper text—ideal for mobile-first layouts.
- **Horizontal fields:** Set `orientation="horizontal"{:tsx}` on `{:tsx}` to align the label and control side-by-side. Pair with `{:tsx}` to keep descriptions aligned.
- **Responsive fields:** Set `orientation="responsive"{:tsx}` for automatic column layouts inside container-aware parents.
## Component Preview: field-responsive-demo
```tsx
"use client";
import { useId } from "react";
import { Field } from "~/components/ui/field";
import { Input } from "~/components/ui/input";
import { Switch } from "~/components/ui/switch";
import { TextField } from "~/components/ui/text-field";
export function FieldResponsiveDemo() {
const switchId = useId();
return (
Account Settings}>
Display name
This is your public display name.
}
>
Email
We'll use this for account notifications.
Two-factor authentication
Add an extra layer of security to your account.
);
}
```
## Validation and Errors
When using `{:tsx}` with React Aria field components like `{:tsx}`, `{:tsx}`, `{:tsx}`, `{:tsx}`, or `
```
If you're not using React Aria field components, you must handle validation state manually:
- Add `data-invalid{:tsx}` to `{:tsx}` to switch the entire block into an error state.
- Add `aria-invalid{:tsx}` on the input itself for assistive technologies.
- Render `{:tsx}` immediately after the control or inside `{:tsx}` to keep error messages aligned with the field.
```tsx
EmailEnter a valid email address.
```
## Accessibility
- `{:tsx}` and `{:tsx}` keep related controls grouped for keyboard and assistive tech users.
- `{:tsx}` outputs `role="group"{:tsx}` so nested controls inherit labeling from `{:tsx}` and `{:tsx}` when combined.
- When using `{:tsx}` with `render={}{:tsx}`, React Aria automatically associates label, description, and error elements via `aria-labelledby{:tsx}` and `aria-describedby{:tsx}`.
## API Reference
### Field.Set
Container that renders a semantic `fieldset` with spacing presets.
| Prop | Type | Default |
| ----------- | -------- | ------- |
| `className` | `string` | |
### Field.Legend
Legend element for a `{:tsx}`. Switch to the `label{:tsx}` variant to align with label sizing.
| Prop | Type | Default |
| ----------- | --------------------- | ---------- |
| `variant` | `"legend" \| "label"` | `"legend"` |
| `className` | `string` | |
### Field.Group
Layout wrapper that stacks `{:tsx}` components and enables container queries for responsive orientations.
| Prop | Type | Default |
| ----------- | -------- | ------- |
| `className` | `string` | |
### Field.Root
The core wrapper for a single field. Provides orientation control, invalid state styling, and spacing.
| Prop | Type | Default |
| -------------- | -------------------------------------------- | ------------ |
| `orientation` | `"vertical" \| "horizontal" \| "responsive"` | `"vertical"` |
| `render` | `React.ReactElement` | |
| `className` | `string` | |
| `data-invalid` | `boolean` | |
Use the `render{:tsx}` prop to compose with React Aria components like `{:tsx}`:
```tsx
}>
Email
```
### Field.Content
Flex column that groups control and descriptions when the label sits beside the control.
| Prop | Type | Default |
| ----------- | -------- | ------- |
| `className` | `string` | |
### Field.Label
Label styled for both direct inputs and nested `Field.Root` children.
| Prop | Type | Default |
| ----------- | -------- | ------- |
| `className` | `string` | |
| `htmlFor` | `string` | |
### Field.Title
Renders a title with label styling inside `{:tsx}`.
| Prop | Type | Default |
| ----------- | -------- | ------- |
| `className` | `string` | |
### Field.Description
Helper text slot. Uses React Aria's `{:tsx}` component with `slot="description"{:tsx}`.
| Prop | Type | Default |
| ----------- | -------- | ------- |
| `className` | `string` | |
### Field.Separator
Visual divider to separate sections inside a `{:tsx}`. Accepts optional inline content.
| Prop | Type | Default |
| ----------- | -------- | ------- |
| `className` | `string` | |
```tsx
Or continue with
```
### Field.Error
Accessible error container that accepts children or an `errors{:tsx}` array.
| Prop | Type | Default |
| ----------- | ------------------------------------------ | ------- |
| `errors` | `Array<{ message?: string } \| undefined>` | |
| `className` | `string` | |
When the `errors{:tsx}` array contains multiple messages, the component renders a list automatically.
```tsx
```
# Form (/docs/components/form)
---
title: Form
description: A form component that coordinates submission and validation.
links:
docs: https://react-aria.adobe.com/Form
---
Form is a **client-only adapter** around React Aria Components. It exists
to: - Explicitly define a client boundary (`"use client"`) - Isolate React
Aria as an implementation detail - Provide a stable public API and
consistent developer experience **Form is a minimal, non-visual coordination
layer.** It handles submission and validation but does not manage field
state or control layout. For field composition, use
[Field](/docs/components/field). See the [Forms architecture
overview](/docs/forms) to understand the complete system.
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/form
```
Copy and paste the following code into your project.
## Component Source: form
```tsx
"use client";
import { Form as RACForm } from "react-aria-components";
export interface FormProps extends React.ComponentProps {}
export function Form(props: FormProps) {
return ;
}
```
## Overview
Form is a **minimal, non-visual** component that coordinates:
- Form submission via `onSubmit`
- Server-side validation errors via `validationErrors`
- Reset handling via `onReset`
Form does **not**:
- Manage field state
- Control layout
- Replace the [Field](/docs/components/field) component
- Render visual elements or field containers
For layout and field composition, use the [Field](/docs/components/field) component.
### Understanding the architecture
Form is **only** responsible for submission and validation coordination. It does not handle:
- Individual field layout → Use [Field](/docs/components/field)
- Input behavior and accessibility → Use [TextField](/docs/components/textfield) or similar
- Complex form state → See [Forms overview](/docs/forms) for library integrations
**Recommended reading:**
- [Forms architecture overview](/docs/forms) – Complete explanation of the three-layer system
- [Field documentation](/docs/components/field) – The composition root for all form fields
## Anatomy
```tsx
import { Button } from "~/components/ui/button";
import { Field } from "~/components/ui/field";
import { Form } from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { TextField } from "~/components/ui/text-field";
;
```
## Form Libraries
For complex forms with client-side validation and state management, see the [Forms](/docs/forms) guide:
- [TanStack Form](/docs/forms/tanstack-form) - Headless form state management
- [React Hook Form](/docs/forms/react-hook-form) - Performance-focused forms
- [React Aria](/docs/forms/react-aria) - Native HTML validation
## Examples
### Basic
## Component Preview: textfield-form
> ⚠️ Component 'textfield-form' not found in registry
### Server Validation
Use the `validationErrors` prop to display server-side validation errors.
```tsx
```
### Reset
```tsx
```
## Props
Form accepts all props from React Aria's [Form](https://react-aria.adobe.com/Form#api).
| Prop | Type | Default | Description |
| -------------------- | ------------------------------------ | ---------- | -------------------------------------- |
| `validationErrors` | `Record` | | Server-side validation errors |
| `validationBehavior` | `"native" \| "aria"` | `"native"` | How validation errors are displayed |
| `onSubmit` | `(e: FormEvent) => void` | | Called when form is submitted |
| `onReset` | `(e: FormEvent) => void` | | Called when form is reset |
| `onInvalid` | `(e: FormEvent) => void` | | Called when form has validation errors |
## Field Examples
See the following components for form integration examples:
- [Checkbox](/docs/components/checkbox)
- [Date Field](/docs/components/date-field)
- [Radio Group](/docs/components/radio-group)
- [Select](/docs/components/select)
- [Switch](/docs/components/switch)
- [Textarea](/docs/components/textarea)
- [TextField](/docs/components/textfield)
# Input Group (/docs/components/input-group)
---
title: Input Group
description: Display additional information or actions to an input or textarea.
links:
docs: https://ui.shadcn.com/docs/components/input-group
---
## Component Preview: input-group-demo
```tsx
import { SearchIcon } from "lucide-react";
import { InputGroup } from "~/components/ui/input-group";
export function InputGroupDemo() {
return (
Search
);
}
```
## Installation
CLIManual
```bash
npx shadcn@latest add @kanpeki/input-group
```
Copy and paste the following code into your project.
## Component Source: input-group
```tsx
// File: index.ts
export * from "./input-group";
export * as InputGroup from "./namespace";
export * from "./styles";
// File: input-group.tsx
"use client";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import type { VariantProps } from "cva";
import { composeRenderProps } from "react-aria-components";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Textarea } from "~/components/ui/textarea";
import { InputGroupStyles } from "./styles";
export interface InputGroupRootProps extends useRender.ComponentProps<"div"> {}
export function InputGroupRoot({
render,
className,
...props
}: InputGroupRootProps) {
const defaultProps: useRender.ElementProps<"div"> = {
className: InputGroupStyles.Root({ className }),
role: "group",
["data-slot" as string]: "input-group-root",
};
return useRender({
defaultTagName: "div",
render,
props: mergeProps<"div">(defaultProps, props),
});
}
export interface InputGroupAddonProps
extends React.ComponentProps<"div">,
VariantProps {}
export function InputGroupAddon({
className,
align = "inline-start",
...props
}: InputGroupAddonProps) {
return (
// biome-ignore lint/a11y/noNoninteractiveElementInteractions: This is fine for this use case.
// biome-ignore lint/a11y/useKeyWithClickEvents: This is fine for this use case.