Components
Tabs
An accessible tabs component that follows the WAI-ARIA Tabs Design Pattern. Each tab in the tab list has associated content, with only the selected tab's content being displayed.
DocsThis is the Account tab content.
This is the Password tab content.
import {
TabsProvider,
TabsRoot,
TabsList,
TabsTrigger,
TabsContent,
} from './tabs';
export default function TabsExample() {
return (
<TabsProvider defaultValue="account">
<TabsRoot className="mx-auto w-full max-w-md">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">
<p>This is the Account tab content.</p>
</TabsContent>
<TabsContent value="password">
<p>This is the Password tab content.</p>
</TabsContent>
</TabsRoot>
</TabsProvider>
);
}Installation
pnpx shadcn@latest add https://ui.marviuz.me/r/tabs.jsonnpx shadcn@latest add https://ui.marviuz.me/r/tabs.jsonyarn dlx shadcn@latest add https://ui.marviuz.me/r/tabs.jsonbunx shadcn@latest add https://ui.marviuz.me/r/tabs.jsonCopy and paste the code below into your project and update imports as needed.
'use client';
import { Slot } from '@radix-ui/react-slot';
import * as tabs from '@zag-js/tabs';
import { normalizeProps, useMachine, type PropTypes } from '@zag-js/react';
import {
createContext,
use,
useId,
type ComponentProps,
type ReactNode,
} from 'react';
import { cn } from '~/lib/utils';
type AsChild = {
asChild?: boolean;
};
const TabsContext = createContext<tabs.Api<PropTypes> | null>(null);
function useTabsApi() {
const ctx = use(TabsContext);
if (!ctx) throw new Error('Must be inside a <TabsProvider>');
return ctx;
}
export type TabsProps = {
children?: ReactNode;
id?: string;
} & Omit<tabs.Props, 'id'>;
export function TabsProvider({ children, id: idProp, ...props }: TabsProps) {
const id = useId();
const service = useMachine(tabs.machine, {
id: idProp ?? id,
...props,
});
const api = tabs.connect(service, normalizeProps);
return <TabsContext value={api}>{children}</TabsContext>;
}
export function TabsRoot({
asChild,
className,
...props
}: ComponentProps<'div'> & AsChild) {
const api = useTabsApi();
const Comp = asChild ? Slot : 'div';
return (
<Comp
className={cn('', className)}
data-slot="tabs-root"
{...api.getRootProps()}
{...props}
/>
);
}
export function TabsList({
asChild,
className,
...props
}: ComponentProps<'div'> & AsChild) {
const api = useTabsApi();
const Comp = asChild ? Slot : 'div';
return (
<Comp
className={cn(
'bg-secondary/25 flex w-fit gap-2 rounded-md border p-1',
className,
)}
data-slot="tabs-list"
{...api.getListProps()}
{...props}
/>
);
}
export function TabsTrigger({
asChild,
value,
disabled,
className,
...props
}: ComponentProps<'button'> & AsChild & tabs.TriggerProps) {
const api = useTabsApi();
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(
'rounded-md border border-transparent px-3 py-1.5 text-xs font-bold transition',
'hover:border-border hover:bg-accent/50',
'aria-selected:bg-accent aria-selected:text-accent-foreground aria-selected:border-border',
className,
)}
data-slot="tabs-trigger"
{...api.getTriggerProps({ value, disabled })}
{...props}
/>
);
}
export function TabsContent({
asChild,
value,
className,
...props
}: ComponentProps<'div'> & AsChild & tabs.ContentProps) {
const api = useTabsApi();
const Comp = asChild ? Slot : 'div';
return (
<Comp
className={cn('mt-4', className)}
data-slot="tabs-content"
{...api.getContentProps({ value })}
{...props}
/>
);
}
export function TabsIndicator({ ...props }: ComponentProps<'div'> & AsChild) {
const api = useTabsApi();
return (
<div data-slot="tabs-indicator" {...api.getIndicatorProps()} {...props} />
);
}
Usage
import {
TabsProvider,
TabsRoot,
TabsList,
TabsTrigger,
TabsContent,
} from './tabs';<TabsProvider defaultValue="account">
<TabsRoot className="mx-auto w-full max-w-md">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">
<p>This is the Account tab content.</p>
</TabsContent>
<TabsContent value="password">
<p>This is the Password tab content.</p>
</TabsContent>
</TabsRoot>
</TabsProvider>Date Picker
A datepicker allows users to enter a date either through text input, or by choosing a date from the calendar.
Tags input
Tag inputs render tags inside an input, followed by an actual text input. By default, tags are added when text is typed in the input field and the Enter or Comma key is pressed. Throughout the interaction, DOM focus remains on the input element.