) => void` | - | Enables built-in drag-and-drop row reorder |
| `dragAndDropHooks` | `DataGridDragAndDropHooks` | - | Advanced React Aria drag-and-drop hooks. Overrides `onReorder` |
| `onRowAction` | `(key: Key) => void` | - | Callback when a row is actioned |
| `renderEmptyState` | `() => ReactNode` | - | Render function for the empty state |
| `onLoadMore` | `() => void` | - | Callback when the load-more sentinel scrolls into view |
| `isLoadingMore` | `boolean` | `false` | Whether more data is currently being fetched |
| `loadMoreContent` | `ReactNode` | - | Content inside the load-more sentinel row |
| `virtualized` | `boolean` | `false` | Enable row virtualization for large datasets |
| `rowHeight` | `number` | `42` | Fixed row height in pixels (virtualized mode) |
| `headingHeight` | `number` | `36` | Header row height in pixels (virtualized mode) |
| `getChildren` | `(item: T) => T[] \| undefined` | - | Return child rows. Enables expandable/tree rows |
| `treeColumn` | `string` | - | Column id that displays the expand/collapse chevron |
| `expandedKeys` | `DataGridSelection` | - | Controlled set of expanded row keys |
| `defaultExpandedKeys` | `DataGridSelection` | - | Default expanded row keys (uncontrolled) |
| `onExpandedChange` | `(keys: DataGridSelection) => void` | - | Callback when expanded rows change |
| `treeIndent` | `number` | `20` | Pixels of indentation per nested level. `0` disables |
### DataGridColumn
Column definition object passed to the `columns` prop.
| Property | Type | Default | Description |
| ----------------- | ---------------------------------- | --------- | ------------------------------------------------------------ |
| `id` | `string` | - | Unique column identifier. Used as the sort key. Required |
| `header` | `ReactNode \| (info) => ReactNode` | - | Header content. Render function receives `{ sortDirection }` |
| `accessorKey` | `keyof T & string` | - | Key on `T` to read the cell value from |
| `cell` | `(item: T, column) => ReactNode` | - | Custom cell renderer |
| `isRowHeader` | `boolean` | `false` | Mark this column as the row header (for accessibility) |
| `allowsSorting` | `boolean` | `false` | Allow this column to be sorted |
| `sortFn` | `(a: T, b: T) => number` | - | Custom sort comparator |
| `allowsResizing` | `boolean` | - | Allow resizing (with `allowsColumnResize` on the grid) |
| `width` | `DataGridColumnSize` | - | Initial column width (px, %, or fr) |
| `minWidth` | `number` | - | Minimum column width |
| `maxWidth` | `number` | - | Maximum column width |
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Cell text alignment |
| `headerClassName` | `string` | - | Additional className for every `` of this column |
| `cellClassName` | `string` | - | Additional className for every ` ` of this column |
| `pinned` | `'start' \| 'end'` | - | Pin this column. Requires numeric `width` or `minWidth` |
### DataGridReorderEvent
Event object passed to the `onReorder` callback.
| Property | Type | Description |
| --------------- | ------------------------------------------------- | ----------------------------------------------------- |
| `keys` | `Set` | The keys that were moved |
| `target` | `{ key: Key; dropPosition: 'before' \| 'after' }` | The target row key and drop position |
| `reorderedData` | `T[]` | The full reordered data array after applying the move |
### Type Exports
| Type | Description |
| -------------------------- | -------------------------------------------------------- |
| `DataGridSelection` | A set of selected keys, or `'all'` |
| `DataGridSortDescriptor` | The current sort column and direction |
| `DataGridSortDirection` | `'ascending' \| 'descending'` |
| `DataGridColumnSize` | A number, numeric string, percentage, or fractional unit |
| `DataGridDragAndDropHooks` | Hooks returned by React Aria's `useDragAndDrop` |
## Related Components
* **Table**: Structured data display in rows and columns
* **Checkbox**: Binary choice input control
* **Chip**: Compact elements for tags and filters
# Kanban
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/kanban
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(data-display)/kanban.mdx
> A drag-and-drop kanban board with columns, cards, and keyboard navigation for organizing tasks and workflows
## Import
```tsx
import { Kanban, useKanban, useKanbanColumn, useKanbanCardPlaceholder } from '@darkcode-ui/react';
```
## Anatomy
The board is composed from compound parts. State is managed by the `useKanban` hook, while each
column wires its drag-and-drop behaviour with `useKanbanColumn`.
```tsx
```
### Usage
Cards can be dragged between columns with a pointer or the keyboard (focus a card, then activate its
drag handle). The board keeps all items in a single list and filters them per column.
```tsx
"use client";
import type {UseKanbanCardPlaceholderReturn, UseKanbanReturn} from "@darkcode-ui/react";
import type {CSSProperties} from "react";
import {
Button,
Chip,
Kanban,
useKanban,
useKanbanCardPlaceholder,
useKanbanColumn,
} from "@darkcode-ui/react";
import {Ellipsis, Plus} from "@gravity-ui/icons";
type TagColor = "accent" | "default" | "success" | "warning" | "danger";
interface Task {
id: string;
title: string;
status: string;
tag: {label: string; color: TagColor};
}
interface Column {
id: string;
title: string;
color: string;
}
const columns: Column[] = [
{color: "var(--muted)", id: "todo", title: "To Do"},
{color: "var(--warning)", id: "in-progress", title: "In Progress"},
{color: "var(--success)", id: "done", title: "Done"},
];
const initialTasks: Task[] = [
{
id: "1",
status: "todo",
tag: {color: "accent", label: "Design"},
title: "Audit the onboarding flow",
},
{
id: "2",
status: "todo",
tag: {color: "warning", label: "Research"},
title: "Interview 5 power users",
},
{
id: "3",
status: "in-progress",
tag: {color: "accent", label: "Design"},
title: "New empty states",
},
{
id: "4",
status: "in-progress",
tag: {color: "success", label: "Eng"},
title: "Wire up drag and drop",
},
{id: "5", status: "done", tag: {color: "success", label: "Eng"}, title: "Set up the board grid"},
];
function KanbanColumn({
column,
kanban,
renderDropIndicator,
}: {
column: Column;
kanban: UseKanbanReturn;
renderDropIndicator: UseKanbanCardPlaceholderReturn["renderDropIndicator"];
}) {
const {dragAndDropHooks, items} = useKanbanColumn(kanban, column.id, {renderDropIndicator});
return (
{column.title}
{items.length}
{(item: Task) => (
{item.tag.label}
{item.title}
)}
Add task
);
}
export function Default() {
const kanban = useKanban({
getColumn: (task) => task.status,
initialItems: initialTasks,
setColumn: (task, status) => ({...task, status}),
});
const {renderDropIndicator} = useKanbanCardPlaceholder({
renderIndicator: (target) => ,
});
return (
{columns.map((column) => (
))}
);
}
```
### Notion Board
```tsx
"use client";
import type {UseKanbanCardPlaceholderReturn, UseKanbanReturn} from "@darkcode-ui/react";
import type {CSSProperties} from "react";
import {
Button,
Chip,
Kanban,
useKanban,
useKanbanCardPlaceholder,
useKanbanColumn,
} from "@darkcode-ui/react";
import {Ellipsis, Plus} from "@gravity-ui/icons";
type TagColor = "accent" | "default" | "success" | "warning" | "danger";
interface Task {
id: string;
title: string;
status: string;
tag: {label: string; color: TagColor};
}
interface Column {
id: string;
title: string;
color: string;
}
const columns: Column[] = [
{color: "var(--muted)", id: "not-started", title: "Not started"},
{color: "var(--accent)", id: "in-progress", title: "In progress"},
{color: "var(--warning)", id: "in-review", title: "In review"},
{color: "var(--success)", id: "done", title: "Done"},
];
const initialTasks: Task[] = [
{
id: "n1",
status: "not-started",
tag: {color: "accent", label: "Marketing"},
title: "Launch announcement post",
},
{
id: "n2",
status: "not-started",
tag: {color: "warning", label: "Ops"},
title: "Update vendor contracts",
},
{
id: "n3",
status: "in-progress",
tag: {color: "success", label: "Product"},
title: "Quarterly roadmap draft",
},
{
id: "n4",
status: "in-review",
tag: {color: "danger", label: "Legal"},
title: "Privacy policy revision",
},
{
id: "n5",
status: "done",
tag: {color: "default", label: "Docs"},
title: "Migrate the help center",
},
];
function KanbanColumn({
column,
kanban,
renderDropIndicator,
}: {
column: Column;
kanban: UseKanbanReturn;
renderDropIndicator: UseKanbanCardPlaceholderReturn["renderDropIndicator"];
}) {
const {dragAndDropHooks, items} = useKanbanColumn(kanban, column.id, {renderDropIndicator});
return (
{column.title}
{items.length}
{(item: Task) => (
{item.title}
{item.tag.label}
)}
New page
);
}
export function NotionBoard() {
const kanban = useKanban({
getColumn: (task) => task.status,
initialItems: initialTasks,
setColumn: (task, status) => ({...task, status}),
});
const {renderDropIndicator} = useKanbanCardPlaceholder({
renderIndicator: (target) => ,
});
return (
{columns.map((column) => (
))}
);
}
```
### Project Board
```tsx
"use client";
import type {UseKanbanCardPlaceholderReturn, UseKanbanReturn} from "@darkcode-ui/react";
import type {CSSProperties} from "react";
import {
Avatar,
Button,
Chip,
Kanban,
useKanban,
useKanbanCardPlaceholder,
useKanbanColumn,
} from "@darkcode-ui/react";
import {Calendar, Comment, Ellipsis, Plus} from "@gravity-ui/icons";
type Priority = "low" | "medium" | "high";
type PriorityColor = "default" | "warning" | "danger";
interface Task {
id: string;
title: string;
status: string;
priority: Priority;
due: string;
comments: number;
assignees: {name: string; src?: string}[];
}
interface Column {
id: string;
title: string;
color: string;
}
const PRIORITY: Record = {
high: {color: "danger", label: "High"},
low: {color: "default", label: "Low"},
medium: {color: "warning", label: "Medium"},
};
const columns: Column[] = [
{color: "var(--muted)", id: "backlog", title: "Backlog"},
{color: "var(--accent)", id: "todo", title: "To Do"},
{color: "var(--warning)", id: "in-progress", title: "In Progress"},
{color: "var(--success)", id: "done", title: "Done"},
];
const initialTasks: Task[] = [
{
assignees: [{name: "Ada Lovelace"}, {name: "Grace Hopper"}],
comments: 3,
due: "Jun 24",
id: "p1",
priority: "high",
status: "backlog",
title: "Define API contract for billing",
},
{
assignees: [{name: "Alan Turing"}],
comments: 1,
due: "Jun 26",
id: "p2",
priority: "medium",
status: "todo",
title: "Build invoice PDF export",
},
{
assignees: [{name: "Katherine Johnson"}, {name: "Edsger Dijkstra"}],
comments: 8,
due: "Jun 21",
id: "p3",
priority: "high",
status: "in-progress",
title: "Migrate auth to the new gateway",
},
{
assignees: [{name: "Barbara Liskov"}],
comments: 0,
due: "Jun 19",
id: "p4",
priority: "low",
status: "done",
title: "Add usage analytics events",
},
];
function Assignees({people}: {people: Task["assignees"]}) {
return (
{people.map((person) => (
{person.src ? : null}
{person.name
.split(" ")
.map((part) => part[0])
.join("")}
))}
);
}
function KanbanColumn({
column,
kanban,
renderDropIndicator,
}: {
column: Column;
kanban: UseKanbanReturn;
renderDropIndicator: UseKanbanCardPlaceholderReturn["renderDropIndicator"];
}) {
const {dragAndDropHooks, items} = useKanbanColumn(kanban, column.id, {renderDropIndicator});
return (
{column.title}
{items.length}
{(item: Task) => (
{PRIORITY[item.priority].label}
{item.due}
{item.title}
)}
Add task
);
}
export function ProjectBoard() {
const kanban = useKanban({
getColumn: (task) => task.status,
initialItems: initialTasks,
setColumn: (task, status) => ({...task, status}),
});
const {renderDropIndicator} = useKanbanCardPlaceholder({
renderIndicator: (target) => ,
});
return (
{columns.map((column) => (
))}
);
}
```
### Sizes
The `size` prop scales column width, card padding, and font size. Use `sm`, `md` (default), or `lg`.
```tsx
"use client";
import type {UseKanbanCardPlaceholderReturn, UseKanbanReturn} from "@darkcode-ui/react";
import type {CSSProperties} from "react";
import {
Chip,
Kanban,
useKanban,
useKanbanCardPlaceholder,
useKanbanColumn,
} from "@darkcode-ui/react";
type TagColor = "accent" | "default" | "success" | "warning" | "danger";
interface Task {
id: string;
title: string;
status: string;
tag: {label: string; color: TagColor};
}
interface Column {
id: string;
title: string;
color: string;
}
const columns: Column[] = [
{color: "var(--muted)", id: "todo", title: "To Do"},
{color: "var(--warning)", id: "in-progress", title: "In Progress"},
{color: "var(--success)", id: "done", title: "Done"},
];
const initialTasks: Task[] = [
{id: "1", status: "todo", tag: {color: "accent", label: "Design"}, title: "Audit onboarding"},
{id: "2", status: "in-progress", tag: {color: "success", label: "Eng"}, title: "Drag and drop"},
{id: "3", status: "done", tag: {color: "success", label: "Eng"}, title: "Board grid"},
];
function KanbanColumn({
column,
kanban,
renderDropIndicator,
}: {
column: Column;
kanban: UseKanbanReturn;
renderDropIndicator: UseKanbanCardPlaceholderReturn["renderDropIndicator"];
}) {
const {dragAndDropHooks, items} = useKanbanColumn(kanban, column.id, {renderDropIndicator});
return (
{column.title}
{items.length}
{(item: Task) => (
{item.tag.label}
{item.title}
)}
);
}
function Board({size}: {size: "sm" | "md" | "lg"}) {
const kanban = useKanban({
getColumn: (task) => task.status,
initialItems: initialTasks,
setColumn: (task, status) => ({...task, status}),
});
const {renderDropIndicator} = useKanbanCardPlaceholder({
renderIndicator: (target) => ,
});
return (
{columns.map((column) => (
))}
);
}
export function Sizes() {
return (
{(["sm", "md", "lg"] as const).map((size) => (
))}
);
}
```
## Related Components
* **List View**: Single-column interactive list with selection and item actions
* **Table**: Structured data display in rows and columns
* **DropZone**: Drag-and-drop file upload area with a file list preview
## Styling
### Passing Tailwind CSS classes
Every part accepts a `className`, and the colored status dot reads the `--kanban-indicator-color`
custom property:
```tsx
Done
```
### Customizing the component classes
To customize the Kanban component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.kanban {
--kanban-column-min-width: 320px;
--kanban-column-gap: 24px;
}
.kanban__card-content {
@apply shadow-none ring-1 ring-border;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and
states are reusable and easy to customize.
### CSS Classes
The Kanban component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/kanban.css)):
#### Base & Size Classes
* `.kanban` - Root board container. CSS grid with horizontal auto-flow and snap scrolling.
* `.kanban--sm` - Small size. Narrower columns (`240px`), tighter gap (`12px`).
* `.kanban--md` - Medium size (default). Standard columns (`280px`), default gap (`16px`).
* `.kanban--lg` - Large size. Wider columns (`320px`), spacious gap (`20px`).
#### Element Classes
* `.kanban__column` - Individual column section.
* `.kanban__column-body` - Visual container for the card list and footer actions.
* `.kanban__column-header` - Header area with indicator, title, count, and actions.
* `.kanban__column-actions` - Hover-reveal action buttons inside the header.
* `.kanban__column-indicator` - Colored status dot (when empty) or icon container (with children).
* `.kanban__column-title` - Column heading text (`text-sm font-semibold`).
* `.kanban__column-count` - Item count badge (`text-xs text-muted`).
* `.kanban__card` - Outer card wrapper. Handles focus ring, cursor, and drag opacity.
* `.kanban__card--sm`, `.kanban__card--md`, `.kanban__card--lg` - Card size variants.
* `.kanban__card-content` - Inner Motion wrapper carrying background, radius, shadow, and layout.
* `.kanban__card-content--sm`, `.kanban__card-content--md`, `.kanban__card-content--lg` - Content size variants.
* `.kanban__card-list` - Scrollable GridList container with vertical gap between cards.
* `.kanban__scroll-shadow` - Optional ScrollShadow wrapper for constrained, scrollable columns.
* `.kanban__drop-indicator` - Custom drop indicator with animated height via `--kanban-drop-height`.
* `.kanban__drag-handle` - Grip button, screen-reader-only by default. Reveals on focus.
* `.kanban__empty` - Empty state placeholder centered inside an empty column.
#### Interactive States
* `.kanban__card:hover .kanban__card-content` - applies `shadow-sm`.
* `.kanban__card[data-selected="true"] .kanban__card-content` - applies `bg-accent-soft`.
* `.kanban__card[data-dragging]` - reduced opacity.
* `.kanban__card[data-focus-visible="true"]` - focus ring with `ring-2`.
* `.kanban__column-header:hover .kanban__column-actions` - opacity transitions to 1.
* `.kanban__drop-indicator[data-drop-target]` - border and animated height.
#### CSS Variables
* `--kanban-column-min-width` - Minimum column width (default: `280px`).
* `--kanban-column-gap` - Gap between columns (default: `16px`).
* `--kanban-column-height` - Column height reference (default: `480px`).
* `--kanban-drop-height` - Drop indicator placeholder height. Set automatically by `useKanbanCardPlaceholder`.
* `--kanban-indicator-color` - Color of the column status dot.
## API Reference
### Kanban
The root board container. Wraps children in a horizontal `ScrollShadow`.
| Prop | Type | Default | Description |
| ------ | ---------------------- | ------- | ------------------------------------------------------------------- |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size variant controlling card padding, font size, and column width. |
Also supports all [ScrollShadow](/docs/react/components/scroll-shadow) props (except `size`).
### Kanban.CardList
The scrollable card list backed by React Aria `GridList`.
| Prop | Type | Default | Description |
| ------------------ | ---------------------------------- | -------- | -------------------------------------------------------- |
| `selectionMode` | `"none" \| "single" \| "multiple"` | `"none"` | Selection behavior for cards. |
| `renderEmptyState` | `() => ReactNode` | — | Custom empty state content when the column has no cards. |
| `dragAndDropHooks` | `DragAndDropHooks` | — | The hooks returned by `useKanbanColumn`. |
Also supports all React Aria [GridList](https://react-spectrum.adobe.com/react-aria/GridList.html) props.
### Kanban.Card
An individual draggable card. Wraps content in a Motion `layout` animation. Backed by React Aria `GridListItem`.
| Prop | Type | Default | Description |
| ------------------ | ------------------------------------------- | ------- | --------------------------------------------------------------------------- |
| `textValue` | `string` | — | Accessible text value for the card. |
| `contentClassName` | `string` | — | Class names for the inner Motion content surface. |
| `children` | `ReactNode \| ((renderProps) => ReactNode)` | — | Card content. May be a render function receiving GridListItem render props. |
Also supports all React Aria [GridListItem](https://react-spectrum.adobe.com/react-aria/GridList.html#gridlistitem) props.
### Kanban.DropIndicator
Visual drop indicator shown during drag operations.
| Prop | Type | Default | Description |
| -------- | ------------ | ------- | ---------------------------------------------------------------------------------------------------- |
| `target` | `DropTarget` | — | The drop target provided by React Aria. |
| `height` | `number` | — | Height of the placeholder in pixels. Typically the dragged card's height via `--kanban-drop-height`. |
### Kanban.DragHandle
Grip button for keyboard-accessible dragging. Screen-reader-only by default; reveals on focus. Uses `slot="drag"`.
Also supports all React Aria [Button](https://react-spectrum.adobe.com/react-aria/Button.html) props.
### Kanban.ScrollShadow
Optional scroll wrapper for constrained column heights. Use with `max-h-[...]` to enable vertical scrolling within a column.
Also supports all [ScrollShadow](/docs/react/components/scroll-shadow) props.
### Other Parts
`Kanban.Column`, `Kanban.ColumnHeader`, `Kanban.ColumnBody`, `Kanban.ColumnActions`,
`Kanban.ColumnIndicator`, `Kanban.ColumnTitle`, and `Kanban.ColumnCount` are styling wrappers that
render `section`, `header`, `div`, `h3`, and `span` elements respectively and accept standard HTML props.
### useKanban
Manages the shared list data for the entire board.
```tsx
const kanban = useKanban({
initialItems: tasks,
getColumn: (item) => item.status,
setColumn: (item, column) => ({ ...item, status: column }),
});
```
| Option | Type | Default | Description |
| -------------- | -------------------------------- | ------------------ | ------------------------------------------------------------ |
| `initialItems` | `T[]` | — | Items to populate the board with. |
| `getColumn` | `(item: T) => string` | — | Return the column identifier for a given item. |
| `setColumn` | `(item: T, column: string) => T` | — | Return a copy of the item assigned to a new column. |
| `dragType` | `string` | `"kanban-item-id"` | Custom drag type for transferring item keys between columns. |
| `getKey` | `(item: T) => string \| number` | `item.id` | Derive a unique key from each item. |
**Returns:** `{ list, addItem, removeItem, moveItem, updateItem, getColumn, setColumn, getKey, dragType }`.
### useKanbanColumn
Provides filtered items and drag-and-drop hooks for a single column.
```tsx
const { items, dragAndDropHooks } = useKanbanColumn(kanban, "in-progress", {
renderDropIndicator,
});
```
| Option | Type | Default | Description |
| ----------------------------- | -------------------------- | ------- | ---------------------------------------------- |
| `kanban` | `UseKanbanReturn` | — | The kanban instance from `useKanban`. |
| `column` | `string` | — | The column identifier to filter items by. |
| `options.renderDropIndicator` | `(target) => ReactElement` | — | Override the default drop indicator rendering. |
**Returns:** `{ items, dragAndDropHooks }` — pass `dragAndDropHooks` to `Kanban.CardList`.
### useKanbanCardPlaceholder
Measures the dragged card's height and writes it as a CSS variable for card-sized drop indicators.
```tsx
const { renderDropIndicator } = useKanbanCardPlaceholder({
renderIndicator: (target) => ,
});
```
| Option | Type | Default | Description |
| ----------------- | -------------------------------------- | ------- | ------------------------------------------------------------- |
| `renderIndicator` | `(target: DropTarget) => ReactElement` | — | Render function that returns a custom `Kanban.DropIndicator`. |
**Returns:** `{ renderDropIndicator }` — pass to `useKanbanColumn` options.
# Number Value
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/number-value
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(data-display)/number-value.mdx
> A formatted number display with locale-aware rendering for currency, percentages, and compact notation.
## Import
```tsx
import { NumberValue } from '@darkcode-ui/react';
```
## Anatomy
Import the NumberValue component and access all parts using dot notation.
> The formatted number is always rendered in the `.number-value__value` element. Add
> `` and `` to place text around it.
```tsx
```
### Usage
By default, `NumberValue` renders a locale-aware decimal number using `Intl.NumberFormat`.
```tsx
import {NumberValue} from "@darkcode-ui/react";
export function NumberValueDefault() {
return (
);
}
```
### Compact
Use `notation="compact"` to render large numbers in short form (e.g. `1.2K`, `1.2M`).
```tsx
import {NumberValue} from "@darkcode-ui/react";
export function NumberValueCompact() {
return (
);
}
```
### Currency
Set `style="currency"` together with a `currency` code to format monetary values.
```tsx
import {NumberValue} from "@darkcode-ui/react";
export function NumberValueCurrency() {
return (
);
}
```
### Format Options
Pass `formatOptions` to access the full `Intl.NumberFormat` API. It overrides the
individual convenience props.
```tsx
import {NumberValue} from "@darkcode-ui/react";
export function NumberValueFormatOptions() {
return (
);
}
```
### Percent
Set `style="percent"` to render a fraction as a percentage.
```tsx
import {NumberValue} from "@darkcode-ui/react";
export function NumberValuePercent() {
return (
);
}
```
### Sign Display
Use `signDisplay` to control when the `+` / `-` sign is shown.
```tsx
import {NumberValue} from "@darkcode-ui/react";
export function NumberValueSignDisplay() {
return (
);
}
```
### Tabular Nums
The value uses tabular figures by default, so digits stay aligned as the number changes.
```tsx
"use client";
import {NumberValue} from "@darkcode-ui/react";
import React from "react";
export function NumberValueTabularNums() {
const [value, setValue] = React.useState(12500.0);
React.useEffect(() => {
const id = setInterval(() => {
setValue((current) => current + (Math.random() - 0.5) * 480);
}, 600);
return () => clearInterval(id);
}, []);
return (
);
}
```
### With Prefix Suffix
Use `NumberValue.Prefix` and `NumberValue.Suffix` to add contextual text around the value.
```tsx
import {NumberValue} from "@darkcode-ui/react";
export function NumberValueWithPrefixSuffix() {
return (
/ 5
~
ms
°F
);
}
```
## Localization
`NumberValue` reads the locale from the nearest
[`I18nProvider`](https://react-spectrum.adobe.com/react-aria/I18nProvider.html). Wrap your app
(or a subtree) to format numbers for a specific locale, or pass the `locale` prop to override
it on a single value.
```tsx
import {I18nProvider} from 'react-aria-components';
import {NumberValue} from '@darkcode-ui/react';
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {NumberValue} from '@darkcode-ui/react';
function CustomNumberValue() {
return (
USD
);
}
```
### Customizing the component classes
To customize the NumberValue component classes, you can use the `@layer components`
directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.number-value {
@apply items-baseline gap-0.5;
}
.number-value__value {
@apply font-semibold tabular-nums;
}
.number-value__prefix,
.number-value__suffix {
@apply text-muted;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The NumberValue component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/number-value.css)):
#### Base Classes
* `.number-value` - Base inline-flex wrapper
#### Element Classes
* `.number-value__prefix` - Text before the formatted number
* `.number-value__value` - The formatted number
* `.number-value__suffix` - Text after the formatted number
## API Reference
### NumberValue
The root component. Formats and displays a number using locale-aware `Intl.NumberFormat`.
| Prop | Type | Default | Description |
| ----------------------- | ------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------- |
| `value` | `number` | - | **Required.** The numeric value to format |
| `style` | `'decimal' \| 'currency' \| 'percent' \| 'unit'` | `'decimal'` | Formatting style |
| `currency` | `string` | - | Currency code (e.g. `"USD"`). Required when `style` is `"currency"` |
| `unit` | `string` | - | Unit type (e.g. `"degree"`). Required when `style` is `"unit"` |
| `notation` | `'standard' \| 'compact' \| 'scientific' \| 'engineering'` | `'standard'` | Notation style |
| `signDisplay` | `'auto' \| 'always' \| 'exceptZero' \| 'never'` | - | Controls when the sign is displayed |
| `minimumFractionDigits` | `number` | - | Minimum number of fraction digits |
| `maximumFractionDigits` | `number` | - | Maximum number of fraction digits |
| `locale` | `string` | - | Override the locale from the nearest I18nProvider |
| `formatOptions` | `Intl.NumberFormatOptions` | - | Format options passed directly to the formatter. Overrides individual convenience props |
| `children` | `React.ReactNode \| ((formatted: string) => React.ReactNode)` | - | Prefix/Suffix sub-components or a render function receiving the formatted string |
Also supports all native `span` HTML attributes except `children` and `style`.
### NumberValue.Prefix
Text displayed before the formatted number.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ----------- |
| `children` | `React.ReactNode` | - | Prefix text |
Also supports all native `span` HTML attributes.
### NumberValue.Suffix
Text displayed after the formatted number.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ----------- |
| `children` | `React.ReactNode` | - | Suffix text |
Also supports all native `span` HTML attributes.
# Table
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/table
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(data-display)/table.mdx
> Tables display structured data in rows and columns with support for sorting, selection, column resizing, and infinite scrolling.
## Import
```tsx
import { Table } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Table} from "@darkcode-ui/react";
export function Basic() {
return (
Name
Role
Status
Email
Kate Moore
CEO
Active
kate@acme.com
John Smith
CTO
Active
john@acme.com
Sara Johnson
CMO
On Leave
sara@acme.com
Michael Brown
CFO
Active
michael@acme.com
);
}
```
### Anatomy
Import the Table component and access all parts using dot notation.
```tsx
import { Table } from '@darkcode-ui/react';
export default () => (
Name
Role
Kate Moore
CEO
{/* Optional footer content */}
);
```
### Secondary Variant
```tsx
import {Table} from "@darkcode-ui/react";
export function SecondaryVariant() {
return (
Name
Role
Status
Email
Kate Moore
CEO
Active
kate@acme.com
John Smith
CTO
Active
john@acme.com
Sara Johnson
CMO
On Leave
sara@acme.com
Michael Brown
CFO
Active
michael@acme.com
);
}
```
### Sorting
Columns can be made sortable using the `allowsSorting` prop on `Table.Column`. Use `sortDescriptor` and `onSortChange` on `Table.Content` to manage sort state.
```tsx
"use client";
import type {SortDescriptor} from "@darkcode-ui/react";
import {Table, cn} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useMemo, useState} from "react";
interface User {
id: number;
name: string;
role: string;
status: string;
email: string;
}
const users: User[] = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "Product Manager",
status: "Inactive",
},
];
function SortableColumnHeader({
children,
sortDirection,
}: {
children: React.ReactNode;
sortDirection?: "ascending" | "descending";
}) {
return (
{children}
{!!sortDirection && (
)}
);
}
export function Sorting() {
const [sortDescriptor, setSortDescriptor] = useState({
column: "name",
direction: "ascending",
});
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => {
const col = sortDescriptor.column as keyof User;
const first = String(a[col]);
const second = String(b[col]);
let cmp = first.localeCompare(second);
if (sortDescriptor.direction === "descending") {
cmp *= -1;
}
return cmp;
});
}, [sortDescriptor]);
return (
{({sortDirection}) => (
Name
)}
{({sortDirection}) => (
Role
)}
{({sortDirection}) => (
Status
)}
{({sortDirection}) => (
Email
)}
{sortedUsers.map((user) => (
{user.name}
{user.role}
{user.status}
{user.email}
))}
);
}
```
### Selection
Enable row selection with `selectionMode` on `Table.Content`. Use `Checkbox` with `slot="selection"` for select-all and per-row checkboxes.
```tsx
"use client";
import type {Selection} from "@darkcode-ui/react";
import {Checkbox, Table} from "@darkcode-ui/react";
import {useState} from "react";
const users = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"},
];
export function SelectionDemo() {
const [selectedKeys, setSelectedKeys] = useState(new Set());
return (
Name
Role
Status
Email
{users.map((user) => (
{user.name}
{user.role}
{user.status}
{user.email}
))}
Selected:{" "}
{selectedKeys === "all"
? "All"
: selectedKeys.size > 0
? Array.from(selectedKeys).join(", ")
: "None"}
);
}
```
### Custom Cells
```tsx
"use client";
import type {Selection, SortDescriptor} from "@darkcode-ui/react";
import {Avatar, Button, Checkbox, Chip, Table, cn} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useMemo, useState} from "react";
interface User {
id: number;
name: string;
image_url: string;
role: string;
status: "Active" | "Inactive" | "On Leave";
email: string;
}
const statusColorMap: Record = {
Active: "success",
Inactive: "danger",
"On Leave": "warning",
};
const users: User[] = [
{
email: "kate@acme.com",
id: 4586932,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/red.jpg",
name: "Kate Moore",
role: "Chief Executive Officer",
status: "Active",
},
{
email: "john@acme.com",
id: 5273849,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/green.jpg",
name: "John Smith",
role: "Chief Technology Officer",
status: "Active",
},
{
email: "sara@acme.com",
id: 7492836,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/blue.jpg",
name: "Sara Johnson",
role: "Chief Marketing Officer",
status: "On Leave",
},
{
email: "michael@acme.com",
id: 8293746,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/purple.jpg",
name: "Michael Brown",
role: "Chief Financial Officer",
status: "Active",
},
{
email: "emily@acme.com",
id: 1234567,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/orange.jpg",
name: "Emily Davis",
role: "Product Manager",
status: "Inactive",
},
];
function SortableColumnHeader({
children,
sortDirection,
}: {
children: React.ReactNode;
sortDirection?: "ascending" | "descending";
}) {
return (
{children}
{!!sortDirection && (
)}
);
}
export function CustomCells() {
const [selectedKeys, setSelectedKeys] = useState(new Set());
const [sortDescriptor, setSortDescriptor] = useState({
column: "name",
direction: "ascending",
});
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => {
const col = sortDescriptor.column as keyof User;
const first = String(a[col]);
const second = String(b[col]);
let cmp = first.localeCompare(second);
if (sortDescriptor.direction === "descending") {
cmp *= -1;
}
return cmp;
});
}, [sortDescriptor]);
return (
{({sortDirection}) => (
Worker ID
)}
{({sortDirection}) => (
Member
)}
{({sortDirection}) => (
Role
)}
{({sortDirection}) => (
Status
)}
Actions
{sortedUsers.map((user) => (
#{user.id.toString()}{" "}
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
{user.name}
{user.email}
{user.role}
{user.status}
))}
);
}
```
### Expandable Rows
Rows can be nested to display hierarchical data. Use the `treeColumn` prop to designate a column, and render a `Button` with `slot="chevron"` in that column’s cells so users can expand and collapse the row. Use the `expandedKeys` prop to control which rows are expanded.
```tsx
"use client";
import type {Selection} from "@darkcode-ui/react";
import {Button, Table, cn} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function ExpandableRows() {
type Row = {
children: Row[];
date: string;
id: string;
title: string;
type: string;
};
const data: Row[] = [
{
children: [
{
children: [
{children: [], date: "7/10/2025", id: "3", title: "Weekly Report", type: "File"},
{children: [], date: "8/20/2025", id: "4", title: "Budget", type: "File"},
],
date: "8/2/2025",
id: "2",
title: "Project",
type: "Directory",
},
],
date: "10/20/2025",
id: "1",
title: "Documents",
type: "Directory",
},
{
children: [
{children: [], date: "1/23/2026", id: "6", title: "Image 1", type: "File"},
{children: [], date: "2/3/2026", id: "7", title: "Image 2", type: "File"},
],
date: "2/3/2026",
id: "5",
title: "Photos",
type: "Directory",
},
];
const [expandedKeys, setExpandedKeys] = useState(() => new Set(["1"]));
const renderExpandableRow = (item: Row) => {
return (
{({hasChildItems, isDisabled, isExpanded, isTreeColumn}) => (
{hasChildItems && isTreeColumn ? (
) : null}
{item.title}
)}
{item.type}
{item.date}
{renderExpandableRow}
);
};
return (
Name
Type
Date Modified
{renderExpandableRow}
);
}
```
### Pagination
Use `Table.Footer` to add a pagination component below the table.
```tsx
"use client";
import {Pagination, Table} from "@darkcode-ui/react";
import {useMemo, useState} from "react";
const columns = [
{id: "name", name: "Name"},
{id: "role", name: "Role"},
{id: "status", name: "Status"},
{id: "email", name: "Email"},
];
const users = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "Product Manager",
status: "Inactive",
},
{email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "Lead Designer", status: "Active"},
{
email: "olivia@acme.com",
id: 7,
name: "Olivia Martinez",
role: "Frontend Engineer",
status: "Active",
},
{
email: "james@acme.com",
id: 8,
name: "James Taylor",
role: "Backend Engineer",
status: "Active",
},
];
const ROWS_PER_PAGE = 4;
export function PaginationDemo() {
const [page, setPage] = useState(1);
const totalPages = Math.ceil(users.length / ROWS_PER_PAGE);
const pages = Array.from({length: totalPages}, (_, i) => i + 1);
const paginatedItems = useMemo(() => {
const start = (page - 1) * ROWS_PER_PAGE;
return users.slice(start, start + ROWS_PER_PAGE);
}, [page]);
const start = (page - 1) * ROWS_PER_PAGE + 1;
const end = Math.min(page * ROWS_PER_PAGE, users.length);
return (
{(column) => (
{column.name}
)}
{(user) => (
{(column) => {user[column.id as keyof typeof user]} }
)}
{start} to {end} of {users.length} results
setPage((p) => Math.max(1, p - 1))}
>
Prev
{pages.map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => Math.min(totalPages, p + 1))}
>
Next
);
}
```
### Column Resizing
Wrap the table in `Table.ResizableContainer` and add `Table.ColumnResizer` inside each resizable column.
```tsx
import {Chip, Table} from "@darkcode-ui/react";
export function ColumnResizing() {
return (
Name
Role
Status
Email
Kate Moore
CEO
Active
kate@acme.com
John Smith
CTO
Active
john@acme.com
Sara Johnson
CMO
On Leave
sara@acme.com
Michael Brown
CFO
Active
michael@acme.com
Emily Davis
Product Manager
Inactive
emily@acme.com
);
}
```
### Empty State
Use `renderEmptyState` on `Table.Body` to display a custom message when the table has no data.
```tsx
"use client";
import {EmptyState, Table} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function EmptyStateDemo() {
return (
Name
Role
Status
Email
(
No results found
)}
>
{[]}
);
}
```
### Async Loading
Use `Table.LoadMore` for infinite scrolling. It renders a sentinel row that triggers `onLoadMore` when scrolled into view.
```tsx
"use client";
import {Chip, Spinner, Table} from "@darkcode-ui/react";
import {useCallback, useRef, useState} from "react";
interface User {
id: number;
name: string;
role: string;
status: string;
email: string;
}
const statusColorMap: Record = {
Active: "success",
Inactive: "danger",
"On Leave": "warning",
};
const allUsers: User[] = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "Product Manager",
status: "Inactive",
},
{email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "Lead Designer", status: "Active"},
{
email: "olivia@acme.com",
id: 7,
name: "Olivia Martinez",
role: "Frontend Engineer",
status: "Active",
},
{
email: "james@acme.com",
id: 8,
name: "James Taylor",
role: "Backend Engineer",
status: "Active",
},
{
email: "sophia@acme.com",
id: 9,
name: "Sophia Anderson",
role: "QA Engineer",
status: "On Leave",
},
{email: "liam@acme.com", id: 10, name: "Liam Thomas", role: "DevOps Engineer", status: "Active"},
{
email: "lucas@acme.com",
id: 11,
name: "Lucas Martinez",
role: "Product Manager",
status: "Active",
},
{
email: "emma@acme.com",
id: 12,
name: "Emma Johnson",
role: "Frontend Engineer",
status: "Active",
},
{email: "noah@acme.com", id: 13, name: "Noah Davis", role: "Backend Engineer", status: "Active"},
{email: "ava@acme.com", id: 14, name: "Ava Wilson", role: "Lead Designer", status: "Active"},
{
email: "oliver@acme.com",
id: 15,
name: "Oliver Martinez",
role: "Frontend Engineer",
status: "Active",
},
{
email: "isabella@acme.com",
id: 16,
name: "Isabella Johnson",
role: "Backend Engineer",
status: "Active",
},
{email: "mia@acme.com", id: 17, name: "Mia Davis", role: "Lead Designer", status: "Active"},
{
email: "william@acme.com",
id: 18,
name: "William Wilson",
role: "Frontend Engineer",
status: "Active",
},
];
const ITEMS_PER_PAGE = 6;
const columns = [
{id: "name", name: "Name"},
{id: "role", name: "Role"},
{id: "status", name: "Status"},
{id: "email", name: "Email"},
];
export function AsyncLoading() {
const [items, setItems] = useState(() => allUsers.slice(0, ITEMS_PER_PAGE));
const [isLoading, setIsLoading] = useState(false);
const isLoadingRef = useRef(false);
const hasMore = items.length < allUsers.length;
const loadMore = useCallback(() => {
if (!hasMore || isLoadingRef.current) return;
isLoadingRef.current = true;
setIsLoading(true);
setTimeout(() => {
setItems((prev) => allUsers.slice(0, prev.length + ITEMS_PER_PAGE));
setIsLoading(false);
requestAnimationFrame(() => {
isLoadingRef.current = false;
});
}, 1500);
}, [hasMore]);
return (
{columns.map((col) => (
{col.name}
))}
{(user) => (
{user.name}
{user.role}
{user.status}
{user.email}
)}
{!!hasMore && (
)}
);
}
```
### Virtualization
Table supports virtualization through [Virtualizer](https://react-aria.adobe.com/Virtualizer), enabling efficient rendering of large datasets by displaying only the rows visible within the viewport.
```tsx
"use client";
import {Table, TableLayout, Virtualizer} from "@darkcode-ui/react";
interface User {
id: number;
name: string;
role: string;
email: string;
}
export function Virtualization() {
const roles = [
"Software Engineer",
"Senior Engineer",
"Staff Engineer",
"Product Manager",
"Designer",
"Data Analyst",
"QA Engineer",
"DevOps Engineer",
"Marketing Manager",
"Sales Representative",
];
const firstNames = [
"Emma",
"Liam",
"Olivia",
"Noah",
"Ava",
"James",
"Sophia",
"Oliver",
"Isabella",
"Lucas",
"Mia",
"Ethan",
"Charlotte",
"Mason",
"Amelia",
"Logan",
"Harper",
"Alexander",
"Ella",
"Benjamin",
];
const lastNames = [
"Smith",
"Johnson",
"Williams",
"Brown",
"Jones",
"Garcia",
"Miller",
"Davis",
"Rodriguez",
"Martinez",
"Anderson",
"Taylor",
"Thomas",
"Jackson",
"White",
"Harris",
"Clark",
"Lewis",
"Robinson",
"Walker",
];
function generateUsers(count: number): User[] {
const users: User[] = [];
for (let i = 0; i < count; i++) {
const firstName = firstNames[i % firstNames.length];
const lastName = lastNames[Math.floor(i / firstNames.length) % lastNames.length];
const name = `${firstName} ${lastName}`;
users.push({
email: `${firstName?.toLowerCase()}.${lastName?.toLowerCase()}@acme.com`,
id: i + 1,
name,
role: roles[i % roles.length] || "",
});
}
return users;
}
const virtualizedUsers = generateUsers(1000);
return (
Name
Role
Email
{(user) => (
{user.name}
{user.role}
{user.email}
)}
);
}
```
### TanStack Table
DarkCode UI's Table works as a rendering layer on top of headless table libraries.
This example uses [TanStack Table](https://tanstack.com/table) for column
definitions, sorting, and pagination — while DarkCode UI handles styling and
accessibility.
```tsx
"use client";
import type {SortDescriptor} from "@darkcode-ui/react";
import type {SortingState} from "@tanstack/react-table";
import {Chip, Pagination, Table, cn} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {useMemo, useState} from "react";
// --- Data -----------------------------------------------------------------
interface User {
id: number;
name: string;
role: string;
status: "Active" | "Inactive" | "On Leave";
email: string;
}
const statusColorMap: Record = {
Active: "success",
Inactive: "danger",
"On Leave": "warning",
};
const users: User[] = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "Product Manager",
status: "Inactive",
},
{email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "Lead Designer", status: "Active"},
{
email: "olivia@acme.com",
id: 7,
name: "Olivia Martinez",
role: "Frontend Engineer",
status: "Active",
},
{
email: "james@acme.com",
id: 8,
name: "James Taylor",
role: "Backend Engineer",
status: "Active",
},
];
// --- TanStack Column Definitions ------------------------------------------
const columnHelper = createColumnHelper();
const columns = [
columnHelper.accessor("name", {header: "Name"}),
columnHelper.accessor("role", {header: "Role"}),
columnHelper.accessor("status", {
cell: (info) => (
{info.getValue()}
),
header: "Status",
}),
columnHelper.accessor("email", {header: "Email"}),
];
// --- Sorting Bridge -------------------------------------------------------
// Convert TanStack SortingState → React Aria SortDescriptor
function toSortDescriptor(sorting: SortingState): SortDescriptor | undefined {
const first = sorting[0];
if (!first) return undefined;
return {
column: first.id,
direction: first.desc ? "descending" : "ascending",
};
}
// Convert React Aria SortDescriptor → TanStack SortingState
function toSortingState(descriptor: SortDescriptor): SortingState {
return [{desc: descriptor.direction === "descending", id: descriptor.column as string}];
}
// --- Sort Header ----------------------------------------------------------
function SortableColumnHeader({
children,
sortDirection,
}: {
children: React.ReactNode;
sortDirection?: "ascending" | "descending";
}) {
return (
{children}
{!!sortDirection && (
)}
);
}
// --- Component ------------------------------------------------------------
const PAGE_SIZE = 4;
export function TanstackTable() {
const [sorting, setSorting] = useState([]);
// eslint-disable-next-line react-hooks/incompatible-library
const table = useReactTable({
columns,
data: users,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
initialState: {pagination: {pageSize: PAGE_SIZE}},
onSortingChange: setSorting,
state: {sorting},
});
const sortDescriptor = useMemo(() => toSortDescriptor(sorting), [sorting]);
const {pageIndex} = table.getState().pagination;
const pageCount = table.getPageCount();
const pages = Array.from({length: pageCount}, (_, i) => i + 1);
const start = pageIndex * PAGE_SIZE + 1;
const end = Math.min((pageIndex + 1) * PAGE_SIZE, users.length);
return (
setSorting(toSortingState(d))}
>
{table.getHeaderGroups()[0]!.headers.map((header) => (
))}
{table.getRowModel().rows.map((row) => (
{row.getVisibleCells().map((cell) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
))}
{start} to {end} of {users.length} results
table.previousPage()}
>
Prev
{pages.map((p) => (
table.setPageIndex(p - 1)}
>
{p}
))}
table.nextPage()}
>
Next
);
}
```
## Related Components
* **Data Grid**: Full-featured data grid with sorting, selection, pinned columns, tree rows, and virtualization
* **Pagination**: Page navigation with composable page links and controls
* **Checkbox**: Binary choice input control
## Styling
### Passing Tailwind CSS classes
You can customize individual Table parts:
```tsx
import { Table } from '@darkcode-ui/react';
function CustomTable() {
return (
);
}
```
### Customizing the component classes
To customize the Table component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.table-root {
@apply relative grid w-full overflow-clip;
}
.table__header {
@apply bg-gray-100;
}
.table__column {
@apply px-4 py-2.5 text-left text-xs font-medium text-gray-600;
}
.table__row {
@apply bg-white border-b border-gray-200;
}
.table__cell {
@apply px-4 py-3 text-sm;
}
.table__footer {
@apply flex items-center px-4 py-2.5;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Table component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/table.css)):
#### Base Classes
* `.table-root` - Root container (named `table-root` instead of `table` because `table` is a built-in Tailwind CSS utility class for `display: table`)
* `.table__scroll-container` - Horizontal scroll wrapper with custom scrollbar
* `.table__content` - The `` element
* `.table__header` - Header row (``)
* `.table__column` - Column header cell (``)
* `.table__body` - Body section (` `)
* `.table__row` - Row element (``)
* `.table__cell` - Data cell (``)
* `.table__footer` - Footer container (outside table)
#### Advanced Classes
* `.table__column-resizer` - Drag handle for column resizing
* `.table__resizable-container` - Wrapper enabling column resizing
* `.table__load-more` - Sentinel row for infinite scrolling
* `.table__load-more-content` - Styled container for the loading indicator
#### Variant Classes
* `.table-root--primary` - Gray background container with card-style body (default)
* `.table-root--secondary` - No background, standalone rounded headers
### Interactive States
The Table supports both CSS pseudo-classes and data attributes for flexibility:
* **Hover**: `:hover` or `[data-hovered="true"]` (row background change)
* **Selected**: `[data-selected="true"]` (row highlight)
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (inset focus ring on rows, columns, and cells)
* **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity)
* **Sortable**: `[data-allows-sorting="true"]` (interactive cursor on columns)
* **Dragging**: `[data-dragging="true"]` (reduced opacity)
* **Drop Target**: `[data-drop-target="true"]` (accent background)
## API Reference
### Table Props
| Prop | Type | Default | Description |
| ----------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant. Primary has a gray background container; secondary is flat with transparent rows. |
| `className` | `string` | - | Additional CSS classes for the root container |
| `children` | `React.ReactNode` | - | Table content (ScrollContainer, Footer, etc.) |
### Table.ScrollContainer Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Table.Content element |
### Table.Content Props
Inherits from [React Aria Table](https://react-spectrum.adobe.com/react-aria/Table.html).
| Prop | Type | Default | Description |
| ------------------- | -------------------------------------- | -------- | ------------------------------ |
| `aria-label` | `string` | - | Accessible label for the table |
| `selectionMode` | `"none" \| "single" \| "multiple"` | `"none"` | Selection behavior |
| `selectedKeys` | `Selection` | - | Controlled selected keys |
| `onSelectionChange` | `(keys: Selection) => void` | - | Selection change handler |
| `sortDescriptor` | `SortDescriptor` | - | Current sort state |
| `onSortChange` | `(descriptor: SortDescriptor) => void` | - | Sort change handler |
| `className` | `string` | - | Additional CSS classes |
### Table.Header Props
Inherits from [React Aria TableHeader](https://react-spectrum.adobe.com/react-aria/Table.html#tableheader).
| Prop | Type | Default | Description |
| ---------- | --------------------------------------------------- | ------- | ------------------------------------------- |
| `columns` | `T[]` | - | Dynamic column data for render prop pattern |
| `children` | `React.ReactNode \| (column: T) => React.ReactNode` | - | Static columns or render prop |
### Table.Column Props
Inherits from [React Aria Column](https://react-spectrum.adobe.com/react-aria/Table.html#column).
| Prop | Type | Default | Description |
| --------------- | ------------------------------------------------------------------- | ------- | ------------------------------------------------- |
| `id` | `string` | - | Column identifier |
| `allowsSorting` | `boolean` | `false` | Whether the column is sortable |
| `isRowHeader` | `boolean` | `false` | Whether this column is a row header |
| `defaultWidth` | `string \| number` | - | Default width for resizable columns |
| `minWidth` | `number` | - | Minimum width for resizable columns |
| `children` | `React.ReactNode \| (values: ColumnRenderProps) => React.ReactNode` | - | Column content or render prop with sort direction |
### Table.Body Props
Inherits from [React Aria TableBody](https://react-spectrum.adobe.com/react-aria/Table.html#tablebody).
| Prop | Type | Default | Description |
| ------------------ | ------------------------------------------------- | ------- | ------------------------------------------ |
| `items` | `T[]` | - | Dynamic row data for render prop pattern |
| `renderEmptyState` | `() => React.ReactNode` | - | Content to display when the table is empty |
| `children` | `React.ReactNode \| (item: T) => React.ReactNode` | - | Static rows or render prop |
### Table.Row Props
Inherits from [React Aria Row](https://react-spectrum.adobe.com/react-aria/Table.html#row).
| Prop | Type | Default | Description |
| ----------- | ------------------ | ------- | ---------------------- |
| `id` | `string \| number` | - | Row identifier |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Row cells |
### Table.Cell Props
Inherits from [React Aria Cell](https://react-spectrum.adobe.com/react-aria/Table.html#cell).
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Cell content |
### Table.Footer Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | --------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Footer content (e.g., pagination) |
### Table.ColumnResizer Props
Inherits from [React Aria ColumnResizer](https://react-spectrum.adobe.com/react-aria/Table.html#columnresizer).
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
### Table.ResizableContainer Props
Inherits from [React Aria ResizableTableContainer](https://react-spectrum.adobe.com/react-aria/Table.html#resizabletablecontainer).
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Table.Content element |
### Table.LoadMore Props
Inherits from [React Aria TableLoadMoreItem](https://react-spectrum.adobe.com/react-aria/Table.html).
| Prop | Type | Default | Description |
| ------------ | ----------------- | ------- | ----------------------------------------------- |
| `isLoading` | `boolean` | `false` | Whether data is currently loading |
| `onLoadMore` | `() => void` | - | Handler called when the sentinel row is visible |
| `children` | `React.ReactNode` | - | Loading indicator content |
### Table.LoadMoreContent Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ----------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Loading indicator content (e.g., Spinner) |
### Table.Collection Props
Re-exported from React Aria `Collection`. Used to render dynamic cells within rows alongside static cells (e.g., checkboxes).
| Prop | Type | Default | Description |
| ---------- | ------------------------------ | ------- | ------------------------- |
| `items` | `T[]` | - | Collection items |
| `children` | `(item: T) => React.ReactNode` | - | Render prop for each item |
### TableLayout
| Name | Type | Default | Description |
| ------------------------ | --------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rowHeight` | `number \| undefined` | 48 | The fixed height of a row in px. |
| `estimatedRowHeight` | `number \| undefined` | — | The estimated height of a row, when row heights are variable. |
| `headingHeight` | `number \| undefined` | 48 | The fixed height of a section header in px. |
| `estimatedHeadingHeight` | `number \| undefined` | — | The estimated height of a section header, when the height is variable. |
| `loaderHeight` | `number \| undefined` | 48 | The fixed height of a loader element in px. This loader is specifically for "load more" elements rendered when loading more rows at the root level or inside nested row/sections. |
| `dropIndicatorThickness` | `number \| undefined` | 2 | The thickness of the drop indicator. |
| `gap` | `number \| undefined` | 0 | The gap between items. |
| `padding` | `number \| undefined` | 0 | The padding around the list. |
# Timeline
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/timeline
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(data-display)/timeline.mdx
> A composable, read-only chronology for activity feeds, audit trails, incident logs, and centered milestone roadmaps.
## Import
```tsx
import { Timeline } from '@darkcode-ui/react';
```
### Usage
Timeline is for read-only chronology: activity feeds, audit trails, incident history, release logs,
and milestone roadmaps. Use [Stepper](/react/components/stepper) when the user is moving through a
known sequence where current, complete, and upcoming steps are the primary meaning.
```tsx
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "warning" | "danger" | "muted";
const events: {title: string; time: string; description: string; status: Status}[] = [
{
description: "Traffic is now served from the new revision across all regions.",
status: "success",
time: "10:24 AM",
title: "Deploy completed",
},
{
description: "The canary build is rolling out to 10% of production pods.",
status: "current",
time: "10:12 AM",
title: "Rolling out canary",
},
{
description: "Container image built and pushed to the registry.",
status: "default",
time: "10:03 AM",
title: "Image built",
},
{
description: "A new commit landed on the main branch.",
status: "muted",
time: "09:58 AM",
title: "Commit pushed",
},
];
export function Default() {
return (
{events.map((event) => (
{event.title}
{event.time}
{event.description}
))}
);
}
```
### Anatomy
Import the Timeline component and access all parts using dot notation.
```tsx
import { Timeline } from '@darkcode-ui/react';
export default () => (
Event title
May 22, 2026
Event details
);
```
Timeline intentionally owns only the chronology layout. It renders an `ol` with `li` items, keeps
connectors decorative, hides empty markers from assistive technology, and marks `status="current"`
items with `aria-current="true"`. Use semantic HTML and other DarkCode UI components such as
[Card](/react/components/card), [Chip](/react/components/chip), and
[Avatar](/react/components/avatar) inside `Timeline.Content` for the event content itself.
The `Timeline.Rail`, `Timeline.Marker`, and `Timeline.Connector` parts are optional — when omitted,
each renders a sensible default. The example below is equivalent to the verbose anatomy above:
```tsx
Event title
```
### Content Subparts
Instead of hand-rolling markup, compose the optional `Timeline.Header`, `Timeline.Title`,
`Timeline.Time`, and `Timeline.Description` parts inside `Timeline.Content`. `Timeline.Time` renders a
semantic `` element and `Timeline.Title` renders an ``.
```tsx
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "muted";
const events: {title: string; time: string; description: string; status: Status}[] = [
{
description: "The release was promoted to production and is serving all traffic.",
status: "success",
time: "10:24 AM",
title: "Released v2.4.0",
},
{
description: "Automated checks and the manual QA pass both completed.",
status: "current",
time: "10:05 AM",
title: "Verifying release candidate",
},
{
description: "The build pipeline produced the candidate artifacts.",
status: "muted",
time: "09:48 AM",
title: "Built artifacts",
},
];
export function ContentSubparts() {
return (
{events.map((event) => (
{event.title}
{event.time}
{event.description}
))}
);
}
```
### Centered Milestones
Set `axis="center"` to place the rail in the middle, and `placement="alternate"` to flip content
from side to side by item index — ideal for roadmaps and milestone timelines.
```tsx
import {Timeline} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
type Status = "default" | "current" | "success";
const milestones: {
title: string;
period: string;
description: string;
status: Status;
icon: string;
}[] = [
{
description: "Project kicked off with the founding team.",
icon: "gravity-ui:flag",
period: "Q1",
status: "success",
title: "Kickoff",
},
{
description: "Closed beta shipped to a handful of design partners.",
icon: "gravity-ui:rocket",
period: "Q2",
status: "success",
title: "Private beta",
},
{
description: "General availability with self-serve onboarding.",
icon: "gravity-ui:star",
period: "Q3",
status: "current",
title: "General availability",
},
{
description: "Enterprise rollout and SOC 2 compliance.",
icon: "gravity-ui:shield-check",
period: "Q4",
status: "default",
title: "Scale",
},
];
export function CenteredMilestones() {
return (
{milestones.map((milestone) => (
{milestone.period}
{milestone.title}
{milestone.description}
))}
);
}
```
### Studio Review
Compose avatars as markers and chips inside `Timeline.Content` to build rich review feeds.
```tsx
import {Avatar, Chip, Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "warning";
type ChipColor = "accent" | "success" | "warning" | "default";
const reviews: {
name: string;
avatar: string;
action: string;
note?: string;
time: string;
status: Status;
tag: {label: string; color: ChipColor};
}[] = [
{
action: "approved the final composition",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-5.jpg",
name: "Kate Morrison",
note: "Lighting and grade look great. Ship it.",
status: "success",
tag: {color: "success", label: "Approved"},
time: "2h ago",
},
{
action: "requested changes on scene 4",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-3.jpg",
name: "John Carter",
note: "The transition feels rushed — can we add 8 frames?",
status: "warning",
tag: {color: "warning", label: "Changes"},
time: "5h ago",
},
{
action: "is reviewing the color pass",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-20.jpg",
name: "Emily Stone",
status: "current",
tag: {color: "accent", label: "In review"},
time: "6h ago",
},
{
action: "uploaded the first cut",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-23.jpg",
name: "Michael Reeves",
status: "default",
tag: {color: "default", label: "Uploaded"},
time: "1d ago",
},
];
export function StudioReview() {
return (
{reviews.map((review) => (
{review.name.charAt(0)}
{review.name} {review.action}
{review.tag.label}
{review.time}
{review.note ? (
“{review.note}”
) : null}
))}
);
}
```
### Expandable
Reveal extra detail on demand. `Timeline.Collapsible` wraps the content in a React Aria Disclosure;
`Timeline.Trigger` toggles it, `Timeline.Indicator` is a chevron that rotates when expanded, and
`Timeline.Details` is the animated panel. Pass `defaultExpanded` (or controlled `isExpanded` /
`onExpandedChange`) to `Timeline.Collapsible`.
```tsx
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "warning";
const events: {
title: string;
time: string;
summary: string;
details: string;
status: Status;
defaultExpanded?: boolean;
}[] = [
{
defaultExpanded: true,
details:
"Migration 0042 added the `events` table and backfilled 1.2M rows in 4m12s with zero downtime. Replicas caught up within seconds.",
status: "success",
summary: "Schema migration applied",
time: "10:24 AM",
title: "Database migrated",
},
{
details:
"Canary received 10% of traffic for 15 minutes. Error rate held at 0.02% and p95 latency stayed under 180ms, so the rollout was promoted.",
status: "current",
summary: "Canary promoted to 100%",
time: "10:12 AM",
title: "Rollout completed",
},
{
details:
"Two flaky integration tests were quarantined before merge. They are tracked in TICKET-318 for follow-up.",
status: "warning",
summary: "Merged with quarantined tests",
time: "09:58 AM",
title: "Pull request merged",
},
];
export function Expandable() {
return (
{events.map((event) => (
{event.title}
{event.time}
{event.summary}
{event.details}
))}
);
}
```
### Interactive
Make an event navigable or clickable with `Timeline.Action`, which renders a React Aria `Button` (or
a `Link` when `href` is set) as a full-width surface with hover, press, and keyboard-focus states.
```tsx
import {Timeline} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
type Status = "default" | "current" | "success" | "warning";
const notifications: {
title: string;
description: string;
time: string;
icon: string;
href: string;
status: Status;
}[] = [
{
description: "Kate approved your pull request #482.",
href: "#pr-482",
icon: "gravity-ui:circle-check",
status: "success",
time: "2m ago",
title: "Review approved",
},
{
description: "The staging deploy is waiting for your sign-off.",
href: "#deploy-staging",
icon: "gravity-ui:rocket",
status: "current",
time: "1h ago",
title: "Deploy needs approval",
},
{
description: "Usage reached 80% of your monthly quota.",
href: "#billing",
icon: "gravity-ui:triangle-exclamation",
status: "warning",
time: "3h ago",
title: "Approaching quota",
},
];
export function Interactive() {
return (
{notifications.map((item) => (
{item.title}
{item.time}
{item.description}
))}
);
}
```
### Compact Log
Use `density="compact"` with `size="sm"` and `align="center"` for terse, log-style chronologies.
```tsx
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "warning" | "danger" | "muted";
const log: {time: string; message: string; status: Status}[] = [
{message: "Health check passed", status: "success", time: "12:04:21"},
{message: "Cache warmed (1,204 keys)", status: "muted", time: "12:04:18"},
{message: "Worker pool scaled to 8", status: "current", time: "12:04:11"},
{message: "Retrying upstream connection", status: "warning", time: "12:03:57"},
{message: "Request timed out (502)", status: "danger", time: "12:03:54"},
{message: "Server started on :8080", status: "default", time: "12:03:40"},
];
export function CompactLog() {
return (
{log.map((entry) => (
{entry.time}
{entry.message}
))}
);
}
```
### Incident Response
Status tones (`success`, `warning`, `danger`, `current`) color both the marker and the connector
leading away from it, making severity easy to scan.
```tsx
import {Chip, Timeline} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
type Status = "default" | "current" | "success" | "warning" | "danger";
type ChipColor = "success" | "warning" | "danger" | "accent";
const incident: {
title: string;
time: string;
detail: string;
icon: string;
status: Status;
tag: {label: string; color: ChipColor};
}[] = [
{
detail: "All systems operational. Post-mortem scheduled for tomorrow.",
icon: "gravity-ui:circle-check",
status: "success",
tag: {color: "success", label: "Resolved"},
time: "14:32",
title: "Incident resolved",
},
{
detail: "Rolled back the payments service to the previous stable release.",
icon: "gravity-ui:arrow-rotate-left",
status: "current",
tag: {color: "accent", label: "Mitigating"},
time: "14:08",
title: "Applying rollback",
},
{
detail: "Error rate exceeded 20% on the checkout endpoint.",
icon: "gravity-ui:triangle-exclamation",
status: "warning",
tag: {color: "warning", label: "Investigating"},
time: "13:55",
title: "Elevated error rate",
},
{
detail: "PagerDuty alerted the on-call engineer. Severity set to SEV-1.",
icon: "gravity-ui:bell-dot",
status: "danger",
tag: {color: "danger", label: "SEV-1"},
time: "13:51",
title: "Incident declared",
},
];
export function IncidentResponse() {
return (
{incident.map((event) => (
{event.title}
{event.tag.label}
{event.time}
{event.detail}
))}
);
}
```
### Grouped Sections
Cluster events under sticky date or category headings with `Timeline.Section` and
`Timeline.SectionHeading`. Each section renders its own ordered sub-list, and item indices stay
continuous across groups (so `useTimelineItem().index` keeps counting). The heading pins to the top
while its group scrolls.
```tsx
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "warning" | "muted";
const groups: {
label: string;
events: {title: string; time: string; description: string; status: Status}[];
}[] = [
{
events: [
{
description: "Promoted the canary to 100% of production traffic.",
status: "success",
time: "10:24 AM",
title: "Released v2.4.0",
},
{
description: "Kate approved the release checklist.",
status: "current",
time: "09:50 AM",
title: "Sign-off received",
},
],
label: "Today",
},
{
events: [
{
description: "Reverted a flaky migration before the freeze.",
status: "warning",
time: "4:12 PM",
title: "Hotfix applied",
},
{
description: "Opened the release branch and cut the candidate.",
status: "muted",
time: "11:03 AM",
title: "Branch cut",
},
],
label: "Yesterday",
},
];
export function GroupedSections() {
return (
{groups.map((group) => (
{group.label}
{group.events.map((event) => (
{event.title}
{event.time}
{event.description}
))}
))}
);
}
```
### Version History
```tsx
import {Avatar, Chip, Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "muted";
const revisions: {
version: string;
summary: string;
author: string;
avatar: string;
time: string;
status: Status;
current?: boolean;
}[] = [
{
author: "Emily Stone",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-20.jpg",
current: true,
status: "current",
summary: "Restructured the pricing section and fixed broken links.",
time: "Today, 9:14 AM",
version: "v4",
},
{
author: "John Carter",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-3.jpg",
status: "default",
summary: "Added the FAQ and clarified the refund policy.",
time: "Yesterday",
version: "v3",
},
{
author: "Kate Morrison",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-5.jpg",
status: "default",
summary: "Copyedited the introduction for tone and length.",
time: "3 days ago",
version: "v2",
},
{
author: "Michael Reeves",
avatar: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-23.jpg",
status: "muted",
summary: "Initial draft of the product overview.",
time: "1 week ago",
version: "v1",
},
];
export function VersionHistory() {
return (
{revisions.map((revision) => (
{revision.version}
{revision.current ? (
Current
) : null}
{revision.time}
{revision.summary}
{revision.author.charAt(0)}
{revision.author}
))}
);
}
```
### Scroll Reveal
Set `reveal` on the root to animate items in as they scroll into view (backed by an
`IntersectionObserver`). The animation is automatically disabled when the user prefers reduced
motion. Override per item with the `reveal` prop on `Timeline.Item`.
```tsx
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "warning" | "muted";
const events: {title: string; time: string; description: string; status: Status}[] = [
{
description: "Promoted the release to all production regions.",
status: "success",
time: "10:24 AM",
title: "Released v2.4.0",
},
{
description: "Canary served 10% of traffic with healthy metrics.",
status: "current",
time: "10:12 AM",
title: "Canary rollout",
},
{
description: "Container image built and pushed to the registry.",
status: "default",
time: "10:03 AM",
title: "Image built",
},
{
description: "Two flaky tests were quarantined before merge.",
status: "warning",
time: "09:58 AM",
title: "Pull request merged",
},
{
description: "Opened the release branch from main.",
status: "muted",
time: "09:40 AM",
title: "Branch cut",
},
{
description: "Sprint planning locked the release scope.",
status: "muted",
time: "09:12 AM",
title: "Scope confirmed",
},
];
export function ScrollReveal() {
return (
{events.map((event) => (
{event.title}
{event.time}
{event.description}
))}
);
}
```
### Horizontal
Set `orientation="horizontal"` to lay the rail along the x-axis with content below each marker —
useful for compact step or progress-style chronologies. The list scrolls horizontally when it
overflows.
```tsx
import {Timeline} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
type Status = "default" | "current" | "success";
const steps: {title: string; time: string; icon: string; status: Status}[] = [
{icon: "gravity-ui:circle-check", status: "success", time: "Mon", title: "Ordered"},
{icon: "gravity-ui:box", status: "success", time: "Tue", title: "Packed"},
{icon: "gravity-ui:truck", status: "current", time: "Wed", title: "Shipped"},
{icon: "gravity-ui:geo-pin", status: "default", time: "Thu", title: "Out for delivery"},
{icon: "gravity-ui:house", status: "default", time: "Fri", title: "Delivered"},
];
export function Horizontal() {
return (
{steps.map((step) => (
{step.title}
{step.time}
))}
);
}
```
### Repository Activity
```tsx
import {Chip, Timeline} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
type Status = "default" | "current" | "success" | "muted";
type ChipColor = "success" | "accent" | "warning" | "default";
const activity: {
title: string;
meta: string;
time: string;
icon: string;
status: Status;
tag?: {label: string; color: ChipColor};
}[] = [
{
icon: "gravity-ui:tag",
meta: "Tagged release v2.4.0",
status: "success",
tag: {color: "success", label: "release"},
time: "12m ago",
title: "Published a release",
},
{
icon: "gravity-ui:code-merge",
meta: "Merged #482 into main",
status: "current",
tag: {color: "accent", label: "merged"},
time: "1h ago",
title: "Merged a pull request",
},
{
icon: "gravity-ui:git-commit",
meta: "feat(timeline): add centered axis",
status: "default",
time: "2h ago",
title: "Pushed 3 commits",
},
{
icon: "gravity-ui:branches",
meta: "Created branch feat/timeline",
status: "muted",
tag: {color: "default", label: "branch"},
time: "5h ago",
title: "Opened a branch",
},
];
export function RepositoryActivity() {
return (
{activity.map((event) => (
{event.title}
{event.tag ? (
{event.tag.label}
) : null}
{event.time}
{event.meta}
))}
);
}
```
### Loading & Load More
Render `Timeline.Skeleton` placeholders while data loads, and `Timeline.LoadMore` to paginate. Pass
`onLoadMore` plus `isLoading`; add `auto` to trigger loading automatically when the row scrolls into
view (infinite scroll).
```tsx
"use client";
import {Timeline} from "@darkcode-ui/react";
import {useState} from "react";
type Status = "default" | "current" | "success" | "warning" | "muted";
const all: {title: string; time: string; description: string; status: Status}[] = [
{
description: "Promoted to all regions.",
status: "success",
time: "10:24 AM",
title: "Released v2.4.0",
},
{
description: "Canary served 10% of traffic.",
status: "current",
time: "10:12 AM",
title: "Canary rollout",
},
{
description: "Image built and pushed.",
status: "default",
time: "10:03 AM",
title: "Image built",
},
{
description: "Quarantined two flaky tests.",
status: "warning",
time: "09:58 AM",
title: "PR merged",
},
{
description: "Opened the release branch.",
status: "muted",
time: "09:40 AM",
title: "Branch cut",
},
{
description: "Sprint scope locked.",
status: "muted",
time: "09:12 AM",
title: "Scope confirmed",
},
];
export function LoadingState() {
const [count, setCount] = useState(2);
const [isLoading, setIsLoading] = useState(false);
const visible = all.slice(0, count);
const hasMore = count < all.length;
const loadMore = () => {
setIsLoading(true);
window.setTimeout(() => {
setCount((c) => Math.min(c + 2, all.length));
setIsLoading(false);
}, 1200);
};
return (
{visible.map((event) => (
{event.title}
{event.time}
{event.description}
))}
{isLoading ? : null}
{hasMore ? : null}
);
}
```
### Virtualized
For very long, data-driven feeds, pass `virtualized` with an `items` array, a `renderItem`
function, an `estimateRowHeight`, and a `maxHeight`. Only the visible rows (plus overscan) are
rendered inside a scroll container, preserving scroll height with spacer rows. Virtualized mode is
best for roughly uniform row heights and is not combined with sections or expandable items.
```tsx
"use client";
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "warning" | "muted";
const statuses: Status[] = ["success", "current", "default", "warning", "muted"];
interface Event {
id: number;
title: string;
time: string;
description: string;
status: Status;
}
const data: Event[] = Array.from({length: 1000}, (_, i) => ({
description: `Automated event #${i + 1} recorded by the pipeline.`,
id: i,
status: statuses[i % statuses.length] as Status,
time: `#${String(i + 1).padStart(4, "0")}`,
title: `Event ${i + 1}`,
}));
export function Virtualized() {
return (
(
{item.title}
{item.time}
{item.description}
)}
/>
);
}
```
### Split Content
In a centered timeline, render two `Timeline.Content` parts with explicit `side` props to place
metadata (such as a timestamp) opposite the main content.
```tsx
import {Timeline} from "@darkcode-ui/react";
type Status = "default" | "current" | "success" | "muted";
const schedule: {time: string; date: string; title: string; description: string; status: Status}[] =
[
{
date: "Mon",
description: "Scope confirmed and tickets created for the sprint.",
status: "success",
time: "09:00",
title: "Kickoff meeting",
},
{
date: "Wed",
description: "First interactive build shared with stakeholders.",
status: "success",
time: "14:30",
title: "Design handoff",
},
{
date: "Thu",
description: "QA is validating the release candidate.",
status: "current",
time: "11:00",
title: "Code freeze",
},
{
date: "Fri",
description: "Ship to production and announce.",
status: "default",
time: "16:00",
title: "Launch",
},
];
export function SplitContent() {
return (
{schedule.map((event) => (
{event.time}
{event.date}
{event.title}
{event.description}
))}
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import { Timeline } from "@darkcode-ui/react";
function ReleaseLog() {
return (
v2.4.0 released
Centered axis and split content shipped.
);
}
```
### Customizing the component classes
To customize the Timeline component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.timeline {
--timeline-row-gap: 2rem;
}
.timeline__marker--current {
@apply bg-accent;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and
states are reusable and easy to customize.
### CSS Classes
The Timeline component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/timeline.css)):
#### Base, Axis & Size Classes
* `.timeline` - Base ordered-list layout (two-column rail + content)
* `.timeline--axis-center` - Centers the rail and enables three-column content placement
* `.timeline--sm` - Small markers and tighter rhythm
* `.timeline--md` - Medium size (default)
* `.timeline--lg` - Large markers and looser rhythm
* `.timeline--compact` - Reduced vertical rhythm between events
#### Element Classes
* `.timeline__item` - A single event row
* `.timeline__rail` - Column holding the marker and connector
* `.timeline__marker` - The event marker dot or icon container
* `.timeline__connector` - The decorative line between markers
* `.timeline__content` - The event content area
#### Status Modifiers
* `.timeline__marker--current` / `--success` / `--warning` / `--danger` / `--muted` - Marker tones
* `.timeline__item--current` / `--success` / `--warning` / `--danger` - Tints the trailing connector
#### Side Modifiers (centered axis)
* `.timeline__content--start` / `.timeline__item--start` - Content occupies the start side
* `.timeline__content--end` / `.timeline__item--end` - Content occupies the end side
* `.timeline__content--align-center` / `.timeline__item--align-center` - Vertically centers content
## API Reference
### Timeline
The root component. Renders an ordered list and provides axis, placement, size, density, and item
alignment context.
| Prop | Type | Default | Description |
| ------------------- | --------------------------------------- | --------------- | ------------------------------------------------------------------- |
| `axis` | `'start' \| 'center'` | `'start'` | Position of the timeline rail |
| `placement` | `'start' \| 'end' \| 'alternate'` | `'end'` | Default side for item content; `alternate` alternates by item index |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Marker and text size |
| `density` | `'compact' \| 'comfortable'` | `'comfortable'` | Vertical rhythm between events |
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout direction of the timeline |
| `itemAlign` | `'start' \| 'center'` | `'start'` | Default vertical alignment for item content |
| `reveal` | `boolean` | `false` | Animate items in as they scroll into view (respects reduced motion) |
| `virtualized` | `boolean` | `false` | Window long feeds (requires `items` + `renderItem`) |
| `items` | `readonly T[]` | - | Data rows for virtualized mode |
| `renderItem` | `(item: T, index: number) => ReactNode` | - | Renders each virtualized row |
| `estimateRowHeight` | `number` | `64` | Estimated row height (px) for virtualization |
| `maxHeight` | `number \| string` | `400` | Scroll-container height for virtualized mode |
| `overscan` | `number` | `5` | Extra rows rendered above/below the viewport |
| `children` | `ReactNode` | - | Timeline items |
Also supports all native `ol` HTML attributes.
### Timeline.Item
A single event in the timeline.
| Prop | Type | Default | Description |
| ---------- | ------------------------------------------------------------------------- | ----------- | ----------------------------------- |
| `status` | `'default' \| 'current' \| 'success' \| 'warning' \| 'danger' \| 'muted'` | `'default'` | Marker tone |
| `align` | `'start' \| 'center'` | inherited | Vertical alignment for item content |
| `side` | `'start' \| 'end'` | inherited | Content side for centered timelines |
| `reveal` | `boolean` | inherited | Animate this item in on scroll |
| `children` | `ReactNode` | - | Item parts |
Also supports all native `li` HTML attributes. Sets `aria-current="true"` when `status="current"`.
### Timeline.Rail
Rail wrapper for the marker and connector. If omitted, `Timeline.Item` renders a default rail.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ---------------------------- |
| `children` | `ReactNode` | - | Marker and connector content |
Also supports all native `span` HTML attributes.
### Timeline.Marker
The event marker. If omitted, `Timeline.Rail` renders a default marker. Empty markers are decorative
and hidden from assistive technology.
| Prop | Type | Default | Description |
| ---------- | ------------------------------------------------------------------------- | ----------- | --------------------- |
| `status` | `'default' \| 'current' \| 'success' \| 'warning' \| 'danger' \| 'muted'` | item status | Marker tone override |
| `children` | `ReactNode` | - | Custom marker content |
Also supports all native `span` HTML attributes.
### Timeline.Connector
Connector line after the current item. It is hidden automatically for the last item.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------------- |
| `force` | `boolean` | `false` | Force rendering even on the last item |
| `children` | `ReactNode` | - | Custom connector content |
Also supports all native `span` HTML attributes.
### Timeline.Content
Main event content. In centered timelines, `side` controls which side of the axis it occupies.
| Prop | Type | Default | Description |
| ---------- | ------------------ | --------- | ------------- |
| `side` | `'start' \| 'end'` | item side | Content side |
| `children` | `ReactNode` | - | Content parts |
Also supports all native `div` HTML attributes.
### Timeline.Header
Groups a title and timestamp on one row. Renders a `div`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | --------------------------------------- |
| `children` | `ReactNode` | - | Header content (typically Title + Time) |
Also supports all native `div` HTML attributes.
### Timeline.Title
The event title. Renders an `h3`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ----------- |
| `children` | `ReactNode` | - | Title text |
Also supports all native `h3` HTML attributes.
### Timeline.Time
A timestamp rendered as a semantic `time` element.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | -------------------------- |
| `dateTime` | `string` | - | Machine-readable timestamp |
| `children` | `ReactNode` | - | Human-readable time |
Also supports all native `time` HTML attributes.
### Timeline.Description
Secondary descriptive text. Renders a `p`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ---------------- |
| `children` | `ReactNode` | - | Description text |
Also supports all native `p` HTML attributes.
### Timeline.Collapsible
Wraps expandable content in a React Aria Disclosure. Place inside `Timeline.Content`.
| Prop | Type | Default | Description |
| ------------------ | ------------------------------- | ------- | ----------------------------------- |
| `isExpanded` | `boolean` | - | Controlled expanded state |
| `defaultExpanded` | `boolean` | `false` | Uncontrolled initial expanded state |
| `onExpandedChange` | `(isExpanded: boolean) => void` | - | Called when expansion changes |
| `isDisabled` | `boolean` | `false` | Disables the disclosure |
| `children` | `ReactNode` | - | Trigger + Details |
### Timeline.Trigger
The button that toggles the collapsible. Renders a React Aria `Button`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ---------------------------------------- |
| `children` | `ReactNode` | - | Trigger content (title, time, indicator) |
Also supports all React Aria `Button` props.
### Timeline.Indicator
A chevron icon that rotates when the collapsible is expanded. Pass a custom element as `children` to
replace the default chevron.
| Prop | Type | Default | Description |
| ---------- | -------------- | ------- | ------------------------ |
| `children` | `ReactElement` | chevron | Custom indicator element |
### Timeline.Details
The animated collapsible panel. Renders a React Aria `DisclosurePanel`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | -------------- |
| `children` | `ReactNode` | - | Detail content |
### Timeline.Action
An interactive item surface. Renders a React Aria `Button`, or a `Link` when `href` is set.
| Prop | Type | Default | Description |
| ------------ | ------------------------- | ------- | -------------------------------------------- |
| `href` | `string` | - | Renders a navigable link instead of a button |
| `onPress` | `(e: PressEvent) => void` | - | Press handler (button mode) |
| `isDisabled` | `boolean` | `false` | Disables the action |
| `children` | `ReactNode` | - | Surface content |
Also supports the underlying React Aria `Button` / `Link` props.
### Timeline.Section
Groups items under a heading with its own continuous rail. Renders an `li` containing a nested `ol`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------------------------- |
| `children` | `ReactNode` | - | A `Timeline.SectionHeading` plus `Timeline.Item`s |
Also supports all native `li` HTML attributes.
### Timeline.SectionHeading
A sticky group label (e.g. a date). Renders a `div`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | --------------- |
| `children` | `ReactNode` | - | Heading content |
Also supports all native `div` HTML attributes.
### Timeline.Skeleton
Renders placeholder rows that mirror the item layout while content loads.
| Prop | Type | Default | Description |
| --------------- | -------------------------------- | ----------- | -------------------------- |
| `count` | `number` | `3` | Number of placeholder rows |
| `animationType` | `'none' \| 'pulse' \| 'shimmer'` | `'shimmer'` | Skeleton animation |
### Timeline.LoadMore
A trailing row that loads the next page, via a button or automatically on scroll.
| Prop | Type | Default | Description |
| ------------ | ------------ | ------------- | -------------------------------------------- |
| `onLoadMore` | `() => void` | - | Called to load the next page |
| `isLoading` | `boolean` | `false` | Shows a spinner instead of the button |
| `auto` | `boolean` | `false` | Trigger `onLoadMore` when scrolled into view |
| `children` | `ReactNode` | `"Load more"` | Button label |
### useTimelineItem
Hook to access per-item context from descendants of `Timeline.Item`.
| Property | Type | Description |
| -------- | ------------------------------------------------------------------------- | --------------------------------------- |
| `align` | `'start' \| 'center'` | Resolved item content alignment |
| `index` | `number` | Zero-based item index |
| `isLast` | `boolean` | Whether this is the final timeline item |
| `side` | `'start' \| 'end'` | Resolved item side |
| `status` | `'default' \| 'current' \| 'success' \| 'warning' \| 'danger' \| 'muted'` | Item marker status |
# Trend Chip
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/trend-chip
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(data-display)/trend-chip.mdx
> A compact chip displaying a trend direction with percentage value, icon indicator, and contextual suffix
## Import
```tsx
import { TrendChip } from '@darkcode-ui/react';
```
## Anatomy
Import the TrendChip component and access all parts using dot notation.
> Plain-text children are automatically wrapped with the `.trend-chip__value` element,
> and a default directional arrow is rendered when no `` is provided.
```tsx
```
### Usage
The `trend` prop drives both the arrow direction and the color: `up` → success,
`down` → danger, `neutral` → warning.
```tsx
import {TrendChip} from "@darkcode-ui/react";
export function TrendChipBasic() {
return (
12.5%
8.2%
0.0%
);
}
```
### Trends
```tsx
import {TrendChip} from "@darkcode-ui/react";
export function TrendChipTrends() {
return (
12.5%
12.5%
12.5%
12.5%
8.2%
8.2%
8.2%
8.2%
);
}
```
### Custom Indicator
Pass a custom icon as the child of `TrendChip.Indicator` to replace the default arrow.
```tsx
import {TrendChip} from "@darkcode-ui/react";
import {ArrowDown, ArrowUp, Minus} from "@gravity-ui/icons";
export function TrendChipCustomIndicator() {
return (
);
}
```
### Prefix and Suffix
Use `TrendChip.Prefix` and `TrendChip.Suffix` to add contextual text around the value.
```tsx
import {TrendChip} from "@darkcode-ui/react";
export function TrendChipPrefixSuffix() {
return (
+
12.5
%
-
340
pts
1,204
sales
);
}
```
### Sizes
```tsx
import {TrendChip} from "@darkcode-ui/react";
export function TrendChipSizes() {
return (
12.5%
12.5%
12.5%
);
}
```
### Variants
```tsx
import {TrendChip} from "@darkcode-ui/react";
const variants = ["primary", "secondary", "tertiary", "soft"] as const;
export function TrendChipVariants() {
return (
{variants.map((variant) => (
12.5%
8.2%
0.0%
))}
);
}
```
## Related Components
* **Avatar**: Display user profile images
* **CloseButton**: Button for dismissing overlays
* **Separator**: Visual divider between content
## Styling
The TrendChip is built on top of the [Chip](/react/components/chip) component, so it
inherits all of Chip's theming, variants, and sizing.
### Passing Tailwind CSS classes
```tsx
import {TrendChip} from '@darkcode-ui/react';
function CustomTrendChip() {
return (
12.5%
);
}
```
### Customizing the component classes
To customize the TrendChip component classes, you can use the `@layer components`
directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.trend-chip {
@apply gap-1.5;
}
.trend-chip__value {
@apply font-semibold tabular-nums;
}
.trend-chip[data-trend="up"] .trend-chip__indicator {
@apply text-success;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The TrendChip component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/trend-chip.css)):
#### Base & Size Classes
* `.trend-chip` - Base chip wrapper
* `.trend-chip--sm` - Small size (default)
* `.trend-chip--md` - Medium size
* `.trend-chip--lg` - Large size
#### Element Classes
* `.trend-chip__indicator` - Trend arrow icon
* `.trend-chip__value` - Numeric value text
* `.trend-chip__prefix` - Text before the value
* `.trend-chip__suffix` - Text after the value
#### Data Attributes
* `[data-trend="up"]` / `[data-trend="down"]` / `[data-trend="neutral"]` on the root - Current trend direction
## API Reference
### TrendChip
The root component. Wraps DarkCode UI [Chip](/react/components/chip) with trend-aware
coloring and arrow icons.
| Prop | Type | Default | Description |
| ---------- | -------------------------------------------------- | -------- | --------------------------------------------------------------------------- |
| `trend` | `"up" \| "down" \| "neutral"` | `"up"` | Trend direction; controls arrow icon and color (success / danger / warning) |
| `size` | `"sm" \| "md" \| "lg"` | `"sm"` | Size variant |
| `variant` | `"primary" \| "secondary" \| "soft" \| "tertiary"` | `"soft"` | Chip style variant |
| `children` | `React.ReactNode` | - | Value text, optional Indicator, Prefix, and Suffix sub-components |
Also supports all DarkCode UI Chip props except `children`, `color`, and `size`.
### TrendChip.Indicator
Custom trend arrow icon. When omitted, a default directional arrow is rendered based on the `trend` prop.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ----------------------- |
| `children` | `React.ReactNode` | - | Custom SVG icon element |
Also supports all native `svg` HTML attributes.
### TrendChip.Prefix
Text displayed before the value.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ----------- |
| `children` | `React.ReactNode` | - | Prefix text |
Also supports all native `span` HTML attributes.
### TrendChip.Suffix
Text displayed after the value.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ----------- |
| `children` | `React.ReactNode` | - | Suffix text |
Also supports all native `span` HTML attributes.
# Agenda
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/agenda
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/agenda.mdx
> Composable calendar with day, week, and month views for displaying and managing events with drag interactions
## Import
```tsx
import { Agenda, useAgenda } from '@darkcode-ui/react';
```
### Usage
Pass an `events` array and the mutation callbacks you want to enable. Drag-to-create, drag-to-move, drag-to-resize, and keyboard deletion are each activated only when the matching callback is provided — omit a callback and its interaction is disabled.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import type {CalendarDateTime} from "@internationalized/date";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
import {useState} from "react";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const initialEvents: AgendaEvent[] = [
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "success", end: at(0, 12), id: "review", start: at(0, 10), title: "Design review"},
{color: "warning", end: at(0, 11, 30), id: "pairing", start: at(0, 10, 30), title: "Pairing"},
{end: at(1, 11), id: "1on1", start: at(1, 10), title: "1:1 with manager"},
{color: "danger", end: at(-1, 16), id: "retro", start: at(-1, 15), title: "Sprint retro"},
{end: at(0, 0), id: "release", isAllDay: true, start: at(0, 0), title: "Release day"},
];
let counter = 0;
export function Basic() {
const [events, setEvents] = useState(initialEvents);
const moveEvent = (id: string, start: CalendarDateTime, end: CalendarDateTime) =>
setEvents((prev) => prev.map((event) => (event.id === id ? {...event, end, start} : event)));
return (
setEvents((prev) => prev.filter((event) => event.id !== id))}
onEventMove={moveEvent}
onEventResize={moveEvent}
onEventCreate={({end, start}) => {
counter += 1;
setEvents((prev) => [...prev, {end, id: `new-${counter}`, start, title: "New event"}]);
}}
/>
);
}
```
### Anatomy
Rendering ` ` without children produces the complete default layout. Every part is also exported for full composition:
```tsx
import {Agenda} from '@darkcode-ui/react';
export default () => (
{/* Day / Week views */}
{/* Month view */}
)
```
For state shared with your own UI, build the state with `useAgenda` and pass it through the `state` prop:
```tsx
const agenda = useAgenda({events, defaultView: "week"});
return (
<>
{agenda.visibleDays.length} day(s) visible
>
);
```
### Views
The agenda supports three views — `day`, `week`, and `month` — switched through `Agenda.ViewSelector` or programmatically via `setView`. Clicking a date number in the month grid navigates to that date in day view, and the first day of each month shows the month name (e.g. "May 1").
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{
color: "success",
end: at(3, 0),
id: "conference",
isAllDay: true,
start: at(1, 0),
title: "Conference",
},
{end: at(0, 0), id: "sprint", isAllDay: true, start: at(-2, 0), title: "Sprint 42"},
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "warning", end: at(0, 12), id: "review", start: at(0, 11), title: "Design review"},
{color: "danger", end: at(0, 15), id: "incident", start: at(0, 14), title: "Incident review"},
{end: at(7, 11), id: "sync", start: at(7, 10), title: "Weekly sync"},
];
export function MonthView() {
return (
);
}
```
### Events
Events are plain objects using `CalendarDateTime` from [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/):
```tsx
interface AgendaEvent {
id: string;
title: string;
start: CalendarDateTime;
end: CalendarDateTime;
color?: string;
isAllDay?: boolean;
isReadOnly?: boolean;
status?: "confirmed" | "unconfirmed";
}
```
Events whose start and end fall on different dates, or with `isAllDay: true`, render as spanning bars in the all-day section (day/week views) or across month rows.
### Drag Interactions
All drag interactions activate after an 8px pointer threshold, so plain clicks never trigger previews:
* **Drag to create** — drag on an empty time slot; a dashed preview snaps to `slotDuration`
* **Drag to move** — drag an event vertically to change its time, or horizontally to move it to another day
* **Drag to resize** — drag the handle at the bottom edge of an event
* **Month move** — drag a month-view event onto another cell; the target highlights via `data-drop-target`
Pressing Escape cancels an in-flight drag. On touch devices, event cards set `touch-action: none` only when draggable, while day columns keep `pan-y` so scrolling always wins over drag-to-create.
### Selection and Deletion
Clicking an event selects it (`data-selected`), clicking empty space deselects, and Escape clears the selection. When `onEventDelete` is provided, Delete or Backspace removes the selected event. Selection state can be controlled via `selectedEventId` and `onEventSelect`.
```tsx
"use client";
import type {AgendaEvent, AgendaView} from "@darkcode-ui/react";
import type {CalendarDate} from "@internationalized/date";
import {Agenda, Button, Description} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
import {useState} from "react";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "success", end: at(0, 12), id: "review", start: at(0, 10), title: "Design review"},
{end: at(1, 11), id: "1on1", start: at(1, 10), title: "1:1 with manager"},
];
export function Controlled() {
const [view, setView] = useState("week");
const [date, setDate] = useState(base);
const [selectedEventId, setSelectedEventId] = useState(null);
return (
setDate(date.add({weeks: 2}))}>
Jump 2 weeks ahead
view: {view} · date: {date.toString()} · selected: {selectedEventId ?? "none"}
);
}
```
### Read Only
Omit the mutation callbacks for a display-only agenda, or set `isReadOnly: true` on individual events to keep them selectable while disabling move/resize and hiding the resize handle.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "success", end: at(0, 12), id: "review", start: at(0, 10), title: "Design review"},
{end: at(0, 15), id: "lunch", isReadOnly: true, start: at(0, 13), title: "Lunch & learn"},
{color: "danger", end: at(1, 11), id: "incident", start: at(1, 10), title: "Incident review"},
];
// Without mutation callbacks the agenda is display-only: events can be
// selected but never created, moved, resized, or deleted.
export function ReadOnly() {
return (
);
}
```
### Unconfirmed Events
`status: "unconfirmed"` renders an event with a dashed border and transparent background to indicate a tentative booking.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{
end: at(0, 11),
id: "planning",
start: at(0, 9),
status: "unconfirmed",
title: "Planning (tentative)",
},
{
color: "danger",
end: at(0, 14),
id: "vendor",
start: at(0, 12, 30),
status: "unconfirmed",
title: "Vendor call (tentative)",
},
{end: at(0, 16), id: "review", start: at(0, 15), title: "Review (confirmed)"},
];
export function UnconfirmedEvents() {
return (
);
}
```
### Custom Hours and Slot Duration
`startHour` and `endHour` clip the visible time range; `slotDuration` controls drag snapping granularity in minutes.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import type {CalendarDateTime} from "@internationalized/date";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
import {useState} from "react";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const initialEvents: AgendaEvent[] = [
{end: at(0, 9, 30), id: "standup", start: at(0, 9), title: "Standup"},
{color: "success", end: at(0, 11, 30), id: "review", start: at(0, 10), title: "Design review"},
{end: at(1, 14, 30), id: "demo", start: at(1, 13, 30), title: "Demo"},
];
let counter = 0;
// The grid shows business hours only, and dragging snaps to 30-minute slots.
export function CustomHours() {
const [events, setEvents] = useState(initialEvents);
const moveEvent = (id: string, start: CalendarDateTime, end: CalendarDateTime) =>
setEvents((prev) => prev.map((event) => (event.id === id ? {...event, end, start} : event)));
return (
{
counter += 1;
setEvents((prev) => [...prev, {end, id: `new-${counter}`, start, title: "New event"}]);
}}
/>
);
}
```
### All-Day Events
All-day and multi-day events stack in a collapsible section pinned above the time grid. When collapsed, per-day event counts are shown — customize them with `collapsedLabel` on `Agenda.AllDaySection`.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const allDay = (
id: string,
title: string,
startDays: number,
endDays: number,
color?: string,
): AgendaEvent => ({
color,
end: toCalendarDateTime(base.add({days: endDays})),
id,
isAllDay: true,
start: toCalendarDateTime(base.add({days: startDays})),
title,
});
const events: AgendaEvent[] = [
allDay("sprint", "Sprint 42", -2, 2),
allDay("travel", "Travel", 0, 1, "warning"),
allDay("holiday", "Public holiday", 2, 2, "danger"),
allDay("workshop", "Workshop", 0, 0, "success"),
];
// The all-day section is collapsible; when collapsed it shows per-day counts
// using the customizable `collapsedLabel`.
export function AllDayEvents() {
return (
);
}
```
### Event Colors
The `color` prop accepts semantic token names (`accent`, `success`, `warning`, `danger`, `default`) or any raw CSS color. Backgrounds are derived with `color-mix`, so a single value themes the whole card.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (hour: number, minute = 0) =>
toCalendarDateTime(base).add({minutes: hour * 60 + minute});
// Semantic token names map to theme colors; any raw CSS color also works.
const colors = ["accent", "success", "warning", "danger", "#8b5cf6", "oklch(0.7 0.15 200)"];
const events: AgendaEvent[] = colors.map((color, index) => ({
color,
end: at(9 + index, 45),
id: `color-${index}`,
start: at(9 + index),
title: color,
}));
export function EventColors() {
return (
);
}
```
### Current Time Indicator
When today is visible, a live indicator marks the current time: a badge in the time column, a faded line across all columns, and a solid segment over today's column. Hour labels near the indicator hide automatically to avoid overlap, and the position updates every minute.
### Weekend Highlighting
Saturday and Sunday columns and month cells receive a subtle tint via the `data-weekend` attribute, computed from the active locale.
### Custom Styles
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "success", end: at(0, 12), id: "review", start: at(0, 10), title: "Design review"},
{end: at(1, 11), id: "1on1", start: at(1, 10), title: "1:1 with manager"},
];
// Sizing is driven by CSS custom properties on the root element.
export function CustomStyles() {
return (
);
}
```
## Styling
### CSS Variables
Sizing is controlled with CSS custom properties on `.agenda`:
| Variable | Default | Description |
| ----------------------------- | --------------------- | ----------------------------------------- |
| `--agenda-slot-height` | `60px` | Height of one hour in the time grid. |
| `--agenda-time-column-width` | `58px` | Width of the hour labels column. |
| `--agenda-current-time-color` | `var(--color-danger)` | Color of the current time indicator. |
| `--agenda-event-radius` | `var(--radius-md)` | Border radius of event cards. |
| `--agenda-week-header-height` | `3.5rem` | Height of the sticky day headers row. |
| `--agenda-all-day-row-height` | `1.625rem` | Height of one all-day/spanning event row. |
### Customizing the component classes
```css
@layer components {
.agenda {
--agenda-slot-height: 90px;
--agenda-current-time-color: var(--color-accent);
}
.agenda__event[data-selected="true"] {
@apply shadow-lg;
}
.agenda__day-column[data-today="true"] {
@apply bg-accent-soft/20;
}
}
```
### CSS Classes
Agenda uses these classes in `packages/styles/components/agenda.css`:
* `.agenda` - Root container; defines the CSS variables above.
* `.agenda__header` - Flex container for heading, view selector, and navigation.
* `.agenda__heading` - Month/year title.
* `.agenda__view-selector` - View switcher (built on ToggleButtonGroup).
* `.agenda__navigation` / `.agenda__nav-button` / `.agenda__today-button` - Navigation controls.
* `.agenda__body` - Bordered content area below the header.
* `.agenda__week-header` / `.agenda__day-header` - Sticky day headers with name and date.
* `.agenda__time-grid` - Scrollable grid container.
* `.agenda__time-labels` / `.agenda__time-label` - Hour labels column.
* `.agenda__day-column` / `.agenda__time-slot` - Day columns and hour rows.
* `.agenda__event` / `.agenda__event-title` / `.agenda__event-time` - Timed event card.
* `.agenda__resize-handle` - Bottom resize handle.
* `.agenda__all-day-section` / `.agenda__all-day-toggle` / `.agenda__all-day-label` / `.agenda__all-day-event` / `.agenda__all-day-summary` - All-day section parts.
* `.agenda__current-time-indicator` / `.agenda__current-time-badge` / `.agenda__current-time-line` / `.agenda__current-time-today-line` - Current time indicator parts.
* `.agenda__month-grid` / `.agenda__month-weekday-header` / `.agenda__month-row` / `.agenda__month-cell` - Month view structure.
* `.agenda__month-cell-date` / `.agenda__month-cell-more` / `.agenda__month-event` / `.agenda__month-spanning-event` - Month view content.
* `.agenda__create-preview` / `.agenda__drop-preview` - Drag previews.
### Interactive States
* **Selected**: `[data-selected="true"]` on event elements
* **Unconfirmed**: `[data-status="unconfirmed"]` — dashed border, transparent background
* **Read only**: `[data-readonly="true"]`
* **Dragging**: `[data-dragging="true"]` on the source event and the root
* **Resizing**: `[data-resizing="true"]`
* **Today**: `[data-today="true"]` on day headers, columns, and month cells
* **Weekend**: `[data-weekend="true"]` on columns and month cells
* **Outside month**: `[data-outside-month="true"]` on month cells
* **Drop target**: `[data-drop-target="true"]` on the hovered month cell during a drag
## API Reference
### useAgenda
The headless hook that owns all agenda state. `Agenda` accepts the same options directly as props, or a prebuilt state via `state`.
| Option | Type | Default | Description |
| ----------------- | ------------------------------ | -------- | ----------------------------------------------------------- |
| `events` | `AgendaEvent[]` | - | Events to display. Required. |
| `defaultView` | `"day" \| "week" \| "month"` | `"week"` | Initial view (uncontrolled). |
| `view` | `"day" \| "week" \| "month"` | - | Controlled view state. |
| `onViewChange` | `(view: AgendaView) => void` | - | Called when the view changes. |
| `defaultDate` | `CalendarDate` | today | Initial focused date (uncontrolled). |
| `date` | `CalendarDate` | - | Controlled date state. |
| `onDateChange` | `(date: CalendarDate) => void` | - | Called when the date changes. |
| `startHour` | `number` | `0` | First visible hour in the time grid. |
| `endHour` | `number` | `24` | Last visible hour in the time grid. |
| `slotDuration` | `number` | `60` | Slot snapping granularity in minutes. |
| `onEventCreate` | `({start, end}) => void` | - | Enables drag-to-create. |
| `onEventMove` | `(id, start, end) => void` | - | Enables drag-to-move (including cross-day and month moves). |
| `onEventResize` | `(id, start, end) => void` | - | Enables drag-to-resize. |
| `onEventDelete` | `(id: string) => void` | - | Enables Delete/Backspace removal of the selected event. |
| `onEventSelect` | `(id: string \| null) => void` | - | Called when selection changes. |
| `selectedEventId` | `string \| null` | - | Controlled selection state. |
The returned state includes `view`, `date`, `setView`, `setDate`, `navigateNext`, `navigatePrevious`, `navigateToday`, `visibleDays`, `visibleWeeks`, `getEventsForDay(day)`, `allDayLayout`, `getMonthRowLayout(week)`, `getPerCellEvents(day)`, `selectEvent`, `deleteSelectedEvent`, and `dragState`.
### Composition Parts
| Component | Description |
| ----------------------------- | ----------------------------------------------------------------------------------- |
| `Agenda.Header` | Container for heading, view selector, and navigation. Renders all three by default. |
| `Agenda.Heading` | Localized month/year title (e.g. "May 2026"). |
| `Agenda.ViewSelector` | Day/Week/Month segmented control; accepts a `labels` map for localization. |
| `Agenda.Navigation` | Wrapper for nav and today buttons. |
| `Agenda.NavButton` | Previous/next button (`slot="previous"` or `slot="next"`). |
| `Agenda.TodayButton` | Navigates to today's date. |
| `Agenda.Body` | Renders the time grid or month grid based on the active view. |
| `Agenda.TimeGrid` | Scrollable day/week grid; owns the drag interactions. |
| `Agenda.WeekHeader` | Sticky row of day headers. |
| `Agenda.AllDaySection` | Collapsible all-day events section (`collapsedLabel` prop). |
| `Agenda.AllDayLabel` | "all-day" label text. |
| `Agenda.AllDayEvent` | Spanning bar (`event`, `colStart`, `colSpan`, `row`). |
| `Agenda.TimeLabels` | Hour labels column. |
| `Agenda.CurrentTimeIndicator` | Live time marker; updates every minute. |
| `Agenda.DayColumn` | One day's column (`date` prop); renders that day's events by default. |
| `Agenda.Event` | Timed event card (`event` prop) with resize handle. |
| `Agenda.MonthGrid` | Month view container; renders weeks by default. |
| `Agenda.MonthWeekdayHeader` | Weekday names row. |
| `Agenda.MonthRow` | One week row (`week` prop for default rendering). |
| `Agenda.MonthSpanningEvent` | Multi-day bar (`event`, `colStart`, `colSpan`, `row`). |
| `Agenda.MonthCell` | Day cell (`date`, `maxEvents`, `moreLabel`, `spanningRowCount`). |
| `Agenda.MonthEvent` | Per-cell event chip with drag-to-move support. |
### Agenda.MonthCell Props
| Prop | Type | Default | Description |
| ------------------ | --------------------------- | ---------- | --------------------------------------------------------------------------------- |
| `date` | `CalendarDate` | - | The date for this cell. Required. |
| `maxEvents` | `number` | `2` | Visible events before overflow; spanning rows above the cell consume this budget. |
| `moreLabel` | `(count: number) => string` | `"N more"` | Label for the overflow link, which navigates to day view. |
| `spanningRowCount` | `number` | `0` | Spanning event rows above this cell (reserves space). |
### Accessibility
* Events are focusable with descriptive labels (title, date, time range, and status); Enter /Space selects.
* Shift +ArrowUp /ArrowDown resizes a focused event by one slot when resizing is enabled.
* Month date numbers and overflow links are buttons that announce the full date.
* Week start, weekday names, weekend detection, and all time formats follow the active locale via `I18nProvider`.
### Related packages
* [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — `CalendarDate` and `CalendarDateTime` types used by all agenda props
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree
# Calendar
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/calendar
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/calendar.mdx
> Composable date picker with month grid, navigation, and year picker support built on React Aria Calendar
## Import
```tsx
import { Calendar } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
export function Basic() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Anatomy
```tsx
import {Calendar} from '@darkcode-ui/react';
export default () => (
{(day) => {day} }
{(date) => }
)
```
### Year Picker
`Calendar.YearPickerTrigger`, `Calendar.YearPickerGrid`, and their body/cell subcomponents provide an integrated year navigation pattern.
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
export function YearPicker() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Default Value
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
import {parseDate} from "@internationalized/date";
export function DefaultValue() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Controlled
Use controlled `value` and `focusedValue` for external state coordination and custom shortcuts.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, ButtonGroup, Calendar, Description} from "@darkcode-ui/react";
import {
getLocalTimeZone,
parseDate,
startOfMonth,
startOfWeek,
today,
} from "@internationalized/date";
import {useState} from "react";
import {useLocale} from "react-aria-components";
export function Controlled() {
const [value, setValue] = useState(null);
const [focusedDate, setFocusedDate] = useState(parseDate("2025-12-25"));
const {locale} = useLocale();
return (
{
const todayDate = today(getLocalTimeZone());
setValue(todayDate);
setFocusedDate(todayDate);
}}
>
Today
{
const nextWeekStart = startOfWeek(today(getLocalTimeZone()), locale);
setValue(nextWeekStart);
setFocusedDate(nextWeekStart);
}}
>
Week
{
const nextMonthStart = startOfMonth(today(getLocalTimeZone()));
setValue(nextMonthStart);
setFocusedDate(nextMonthStart);
}}
>
Month
{(day) => {day} }
{(date) => }
Selected date: {value ? value.toString() : "(none)"}
{
const todayDate = today(getLocalTimeZone());
setValue(todayDate);
setFocusedDate(todayDate);
}}
>
Set Today
{
const christmasDate = parseDate("2025-12-25");
setValue(christmasDate);
setFocusedDate(christmasDate);
}}
>
Set Christmas
setValue(null)}>
Clear
);
}
```
### Multiple Selection
Set `selectionMode="multiple"` to let users select several dates at once. In this mode `value`, `defaultValue`, and `onChange` work with an array of dates (`DateValue[]`) instead of a single date.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar, Description} from "@darkcode-ui/react";
import {parseDate} from "@internationalized/date";
import {useState} from "react";
export function MultipleSelection() {
const [value, setValue] = useState([
parseDate("2025-02-10"),
parseDate("2025-02-14"),
parseDate("2025-02-21"),
]);
return (
{(day) => {day} }
{(date) => }
Selected dates: {value.length ? value.map((date) => date.toString()).join(", ") : "(none)"}
);
}
```
### Min and Max Dates
```tsx
"use client";
import {Calendar, Description} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function MinMaxDates() {
const now = today(getLocalTimeZone());
const minDate = now;
const maxDate = now.add({months: 3});
return (
{(day) => {day} }
{(date) => }
Select a date between today and {maxDate.toString()}
);
}
```
### Unavailable Dates
Use `isDateUnavailable` to block dates such as weekends, holidays, or booked slots.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar, Description} from "@darkcode-ui/react";
import {isWeekend} from "@internationalized/date";
import {useLocale} from "react-aria-components";
export function UnavailableDates() {
const {locale} = useLocale();
const isDateUnavailable = (date: DateValue) => isWeekend(date, locale);
return (
{(day) => {day} }
{(date) => }
Weekends are unavailable
);
}
```
### Disabled
```tsx
"use client";
import {Calendar, Description} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
return (
{(day) => {day} }
{(date) => }
Calendar is disabled
);
}
```
### Read Only
```tsx
"use client";
import {Calendar, Description} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function ReadOnly() {
return (
{(day) => {day} }
{(date) => }
Calendar is read-only
);
}
```
### Focused Value
Programmatically control which date is focused using `focusedValue` and `onFocusChange`.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Calendar, Description} from "@darkcode-ui/react";
import {parseDate} from "@internationalized/date";
import {useState} from "react";
export function FocusedValue() {
const [focusedDate, setFocusedDate] = useState(parseDate("2025-06-15"));
return (
{(day) => {day} }
{(date) => }
Focused: {focusedDate.toString()}
setFocusedDate(parseDate("2025-01-01"))}
>
Go to Jan
setFocusedDate(parseDate("2025-06-15"))}
>
Go to Jun
setFocusedDate(parseDate("2025-12-25"))}
>
Go to Christmas
);
}
```
### Cell Indicators
You can customize `Calendar.Cell` children and use `Calendar.CellIndicator` to display metadata like events.
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
import {getLocalTimeZone, isToday} from "@internationalized/date";
const datesWithEvents = [3, 7, 12, 15, 21, 28];
export function WithIndicators() {
return (
{(day) => {day} }
{(date) => (
{({formattedDate}) => (
<>
{formattedDate}
{(isToday(date, getLocalTimeZone()) || datesWithEvents.includes(date.day)) && (
)}
>
)}
)}
);
}
```
### Multiple Months
Render multiple grids with `visibleDuration` and `offset` for booking and planning experiences.
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
import {getLocalTimeZone} from "@internationalized/date";
import React from "react";
import {CalendarStateContext, useLocale} from "react-aria-components";
function CalendarMonthHeading({offset = 0}: {offset?: number}) {
const state = React.useContext(CalendarStateContext)!;
const {locale} = useLocale();
const startDate = state.visibleRange.start;
const monthDate = startDate.add({months: offset});
const dateObj = monthDate.toDate(getLocalTimeZone());
const monthYear = new Intl.DateTimeFormat(locale, {month: "long", year: "numeric"}).format(
dateObj,
);
return {monthYear} ;
}
export function MultipleMonths() {
return (
{(day) => {day} }
{(date) => }
{(day) => {day} }
{(date) => }
);
}
```
### Week View
Show a fixed number of weeks by passing `visibleDuration={{weeks: n}}`. The grid keeps its seven columns and renders the requested number of rows.
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
export function WeekView() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Day View
Show a horizontal strip of days by passing `visibleDuration={{days: n}}`. The column count follows the visible day span (capped at seven so week-or-longer ranges stay week-aligned).
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
export function DayView() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### International Calendars
By default, Calendar displays dates using the calendar system for the user's locale. You can override this by wrapping your Calendar with `I18nProvider` and setting the [Unicode calendar locale extension](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string).
The example below shows the Indian calendar system:
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**Note:** The `onChange` event always returns a date in the same calendar system as the `value` or `defaultValue` (Gregorian if no value is provided), regardless of the displayed locale. This ensures your application logic works consistently with a single calendar system while still displaying dates in the user's preferred format.
### Custom Navigation Icons
Pass children to `Calendar.NavButton` to replace the default chevron icons.
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
export function CustomIcons() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Real-World Example
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Calendar} from "@darkcode-ui/react";
import {getLocalTimeZone, isWeekend, today} from "@internationalized/date";
import {useState} from "react";
import {useLocale} from "react-aria-components";
export function BookingCalendar() {
const [selectedDate, setSelectedDate] = useState(null);
const {locale} = useLocale();
const bookedDates = [5, 6, 12, 13, 14, 20];
const isDateUnavailable = (date: DateValue) => {
return isWeekend(date, locale) || bookedDates.includes(date.day);
};
return (
{(day) => {day} }
{(date) => (
{({formattedDate, isUnavailable}) => (
<>
{formattedDate}
{!isUnavailable &&
!isWeekend(date, locale) &&
bookedDates.includes(date.day) && }
>
)}
)}
Has bookings
Weekend/Unavailable
{selectedDate ? (
Book {selectedDate.toString()}
) : null}
);
}
```
### Custom Styles
```tsx
"use client";
import {Calendar} from "@darkcode-ui/react";
export function CustomStyles() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
## Related Components
* **RangeCalendar**: Interactive month grid for selecting date ranges
* **DateField**: Date input field with labels, descriptions, and validation
* **DatePicker**: Composable date picker with date field trigger and calendar popover
## Styling
### Passing Tailwind CSS classes
```tsx
import {Calendar} from '@darkcode-ui/react';
function CustomCalendar() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Customizing the component classes
```css
@layer components {
.calendar {
@apply w-72 rounded-2xl border border-border bg-surface p-3 shadow-sm;
}
.calendar__heading {
@apply text-sm font-semibold text-default-700;
}
.calendar__cell[data-selected="true"] {
@apply bg-accent text-accent-foreground;
}
}
```
### CSS Classes
Calendar uses these classes in `packages/styles/components/calendar.css` and `packages/styles/components/calendar-year-picker.css`:
* `.calendar` - Root container.
* `.calendar__header` - Header row containing nav buttons and heading.
* `.calendar__heading` - Current month label.
* `.calendar__nav-button` - Previous/next navigation controls.
* `.calendar__grid` - Main day grid.
* `.calendar__grid-header` - Weekday header row wrapper.
* `.calendar__grid-body` - Date rows wrapper.
* `.calendar__header-cell` - Weekday header cell.
* `.calendar__cell` - Interactive day cell.
* `.calendar__cell-indicator` - Dot indicator inside a day cell.
* `.calendar-year-picker__trigger` - Year picker toggle button.
* `.calendar-year-picker__trigger-heading` - Heading text inside year picker trigger.
* `.calendar-year-picker__trigger-indicator` - Indicator icon inside year picker trigger.
* `.calendar-year-picker__year-grid` - Overlay grid of selectable years.
* `.calendar-year-picker__year-cell` - Individual year option.
### Interactive States
Calendar supports both pseudo-classes and React Aria data attributes:
* **Selected**: `[data-selected="true"]`
* **Today**: `[data-today="true"]`
* **Unavailable**: `[data-unavailable="true"]`
* **Outside month**: `[data-outside-month="true"]`
* **Hovered**: `:hover` or `[data-hovered="true"]`
* **Pressed**: `:active` or `[data-pressed="true"]`
* **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]`
* **Disabled**: `:disabled` or `[data-disabled="true"]`
## API Reference
### Calendar Props
Calendar inherits all props from React Aria [Calendar](https://react-spectrum.adobe.com/react-aria/Calendar.html).
| Prop | Type | Default | Description |
| ------------------------ | ------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------- |
| `selectionMode` | `'single' \| 'multiple'` | `'single'` | Whether one date or several dates can be selected. |
| `value` | `DateValue \| null \| DateValue[]` | - | Controlled selected date(s). An array when `selectionMode="multiple"`. |
| `defaultValue` | `DateValue \| null \| DateValue[]` | - | Initial selected date(s), uncontrolled. An array when `selectionMode="multiple"`. |
| `onChange` | `(value: DateValue \| DateValue[]) => void` | - | Called when selection changes. Receives an array when `selectionMode="multiple"`. |
| `focusedValue` | `DateValue` | - | Controlled focused date. |
| `onFocusChange` | `(value: DateValue) => void` | - | Called when focus moves to another date. |
| `minValue` | `DateValue` | Calendar-aware `1900-01-01` | Earliest selectable date. |
| `maxValue` | `DateValue` | Calendar-aware `2099-12-31` | Latest selectable date. |
| `isDateUnavailable` | `(date: DateValue) => boolean` | - | Marks dates as unavailable. |
| `isDisabled` | `boolean` | `false` | Disables interaction and selection. |
| `isReadOnly` | `boolean` | `false` | Keeps content readable but prevents selection changes. |
| `isInvalid` | `boolean` | `false` | Marks the calendar as invalid for validation UI. |
| `visibleDuration` | `{months?: number}` | `{months: 1}` | Number of visible months. |
| `defaultYearPickerOpen` | `boolean` | `false` | Initial open state of internal year picker. |
| `isYearPickerOpen` | `boolean` | - | Controlled year picker open state. |
| `onYearPickerOpenChange` | `(isOpen: boolean) => void` | - | Called when year picker open state changes. |
### Composition Parts
| Component | Description |
| ------------------------------------- | -------------------------------------------------------------------------- |
| `Calendar.Header` | Header container for navigation and heading. |
| `Calendar.Heading` | Current month/year heading. |
| `Calendar.NavButton` | Previous/next navigation control (`slot=\"previous\"` or `slot=\"next\"`). |
| `Calendar.Grid` | Day grid for one month (`offset` supported for multi-month layouts). |
| `Calendar.GridHeader` | Weekday header container. |
| `Calendar.GridBody` | Date cell body container. |
| `Calendar.HeaderCell` | Weekday label cell. |
| `Calendar.Cell` | Individual date cell. |
| `Calendar.CellIndicator` | Optional indicator element for custom metadata. |
| `Calendar.YearPickerTrigger` | Trigger to toggle year-picker mode. |
| `Calendar.YearPickerTriggerHeading` | Localized heading content inside the year-picker trigger. |
| `Calendar.YearPickerTriggerIndicator` | Toggle icon inside the year-picker trigger. |
| `Calendar.YearPickerGrid` | Overlay year selection grid container. |
| `Calendar.YearPickerGridBody` | Body renderer for year grid cells. |
| `Calendar.YearPickerCell` | Individual year option cell. |
### Calendar.Cell Render Props
When `Calendar.Cell` children is a function, React Aria render props are available:
| Prop | Type | Description |
| ---------------- | --------- | ------------------------------------------- |
| `formattedDate` | `string` | Localized day label for the cell. |
| `isSelected` | `boolean` | Whether the date is selected. |
| `isUnavailable` | `boolean` | Whether the date is unavailable. |
| `isDisabled` | `boolean` | Whether the cell is disabled. |
| `isOutsideMonth` | `boolean` | Whether the date belongs to adjacent month. |
For a complete list of supported calendar systems and their identifiers, see:
* [React Aria Calendar Implementations](https://react-aria.adobe.com/internationalized/date/Calendar#implementations)
* [React Aria International Calendars](https://react-aria.adobe.com/Calendar#international-calendars)
### Related packages
* [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — date types (`CalendarDate`, `CalendarDateTime`, `ZonedDateTime`) and utilities used by all date components
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree
* [`useLocale`](https://react-aria.adobe.com/useLocale) — read the current locale and layout direction
# DateField
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/date-field
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/date-field.mdx
> Date input field with labels, descriptions, and validation built on React Aria DateField
## Import
```tsx
import { DateField } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {DateField, Label} from "@darkcode-ui/react";
export function Basic() {
return (
Date
{(segment) => }
);
}
```
### Anatomy
```tsx
import {DateField, Label, Description, FieldError} from '@darkcode-ui/react';
export default () => (
{(segment) => }
)
```
> **DateField** combines label, date input, description, and error into a single accessible component.
### With Description
```tsx
"use client";
import {DateField, Description, Label} from "@darkcode-ui/react";
export function WithDescription() {
return (
Birth date
{(segment) => }
Enter your date of birth
Appointment date
{(segment) => }
Enter a date for your appointment
);
}
```
### Required Field
```tsx
"use client";
import {DateField, Description, Label} from "@darkcode-ui/react";
export function Required() {
return (
Date
{(segment) => }
Start date
{(segment) => }
Required field
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
"use client";
import {DateField, FieldError, Label} from "@darkcode-ui/react";
export function Invalid() {
return (
Date
{(segment) => }
Please enter a valid date
Date
{(segment) => }
Date must be in the future
);
}
```
### With Validation
DateField supports validation with `minValue`, `maxValue`, and custom validation logic.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {DateField, Description, FieldError, Label} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function WithValidation() {
const [value, setValue] = useState(null);
const todayDate = today(getLocalTimeZone());
const isInvalid = value !== null && value.compare(todayDate) < 0;
return (
Date
{(segment) => }
{isInvalid ? (
Date must be today or in the future
) : (
Enter a date from today onwards
)}
);
}
```
### Granularity
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {DateField, Label, ListBox, Select, Tooltip} from "@darkcode-ui/react";
import {CircleQuestion} from "@gravity-ui/icons";
import {parseDate, parseZonedDateTime} from "@internationalized/date";
import {useState} from "react";
export function Granularity() {
const granularityOptions = [
{id: "day", label: "Day"},
{id: "hour", label: "Hour"},
{id: "minute", label: "Minute"},
{id: "second", label: "Second"},
] as const;
const [granularity, setGranularity] = useState<"day" | "hour" | "minute" | "second">("day");
// Determine appropriate default value based on granularity
let defaultValue: DateValue;
if (granularity === "day") {
defaultValue = parseDate("2025-02-03");
} else {
// hour, minute, second
defaultValue = parseZonedDateTime("2025-02-03T08:45:00[America/Los_Angeles]");
}
return (
Appointment Date
{(segment) => }
Granularity
Determines the smallest unit displayed in the date picker. By default, this is "day"
for dates, and "minute" for times.
setGranularity(value as typeof granularity)}
>
{granularityOptions.map((option) => (
{option.label}
))}
);
}
```
### Controlled
Control the value to synchronize with other components or state management.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, DateField, Description, Label} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(null);
return (
Date
{(segment) => }
Current value: {value ? value.toString() : "(empty)"}
setValue(today(getLocalTimeZone()))}>
Set today
setValue(null)}>
Clear
);
}
```
### Disabled State
```tsx
"use client";
import {DateField, Description, Label} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
return (
Date
{(segment) => }
This date field is disabled
Date
{(segment) => }
This date field is disabled
);
}
```
### With Icons
Add prefix or suffix icons to enhance the date field.
```tsx
"use client";
import {DateField, Label} from "@darkcode-ui/react";
import {Calendar} from "@gravity-ui/icons";
export function WithPrefixIcon() {
return (
Date
{(segment) => }
);
}
```
```tsx
"use client";
import {DateField, Label} from "@darkcode-ui/react";
import {Calendar} from "@gravity-ui/icons";
export function WithSuffixIcon() {
return (
Date
{(segment) => }
);
}
```
```tsx
"use client";
import {DateField, Description, Label} from "@darkcode-ui/react";
import {Calendar, ChevronDown} from "@gravity-ui/icons";
export function WithPrefixAndSuffix() {
return (
Date
{(segment) => }
Enter a date
);
}
```
### Full Width
```tsx
"use client";
import {DateField, Label} from "@darkcode-ui/react";
import {Calendar, ChevronDown} from "@gravity-ui/icons";
export function FullWidth() {
return (
Date
{(segment) => }
Date
{(segment) => }
);
}
```
### Variants
The DateField.Group component supports two visual variants:
* **`primary`** (default) - Standard styling with shadow, suitable for most use cases
* **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
"use client";
import {DateField, Label} from "@darkcode-ui/react";
export function Variants() {
return (
Primary variant
{(segment) => }
Secondary variant
{(segment) => }
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` on DateField.Group to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {DateField, Description, Label, Surface} from "@darkcode-ui/react";
import {Calendar} from "@gravity-ui/icons";
export function OnSurface() {
return (
Date
{(segment) => }
Enter a date
Appointment date
{(segment) => }
Enter a date for your appointment
);
}
```
### Form Example
Complete form example with validation and submission handling.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, DateField, Description, FieldError, Form, Label} from "@darkcode-ui/react";
import {Calendar} from "@gravity-ui/icons";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function FormExample() {
const [value, setValue] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const todayDate = today(getLocalTimeZone());
const isInvalid = value !== null && value.compare(todayDate) < 0;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("Date submitted:", {date: value});
setValue(null);
setIsSubmitting(false);
}, 1500);
};
return (
);
}
```
## Related Components
* **DatePicker**: Composable date picker with date field trigger and calendar popover
* **Calendar**: Interactive month grid for selecting dates
* **Label**: Accessible label for form controls
### Custom Render Function
```tsx
"use client";
import {DateField, Label} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}
>
}>Date
}>
}>
{(segment) => }
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {DateField, Label, Description} from '@darkcode-ui/react';
function CustomDateField() {
return (
Appointment date
{(segment) => }
Select a date for your appointment.
);
}
```
### Customizing the component classes
DateField has minimal default styling. Override the `.date-field` class to customize the container styling.
```css
@layer components {
.date-field {
@apply flex flex-col gap-1;
&[data-invalid="true"],
&[aria-invalid="true"] {
[data-slot="description"] {
@apply hidden;
}
}
[data-slot="label"] {
@apply w-fit;
}
[data-slot="description"] {
@apply px-1;
}
}
}
```
### CSS Classes
* `.date-field` – Root container with minimal styling (`flex flex-col gap-1`)
> **Note:** Child components ([Label](/docs/components/label), [Description](/docs/components/description), [FieldError](/docs/components/field-error)) have their own CSS classes and styling. See their respective documentation for customization options. DateField.Group styling is documented below in the API Reference section.
### Interactive States
DateField automatically manages these data attributes based on its state:
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` - Automatically hides the description slot when invalid
* **Required**: `[data-required="true"]` - Applied when `isRequired` is true
* **Disabled**: `[data-disabled="true"]` - Applied when `isDisabled` is true
* **Focus Within**: `[data-focus-within="true"]` - Applied when any child input is focused
## API Reference
### DateField Props
DateField inherits all props from React Aria's [DateField](https://react-aria.adobe.com/DateField.md) component.
#### Base Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------ | ------- | ------------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: DateFieldRenderProps) => React.ReactNode` | - | Child components (Label, DateField.Group, etc.) or render function. |
| `className` | `string \| (values: DateFieldRenderProps) => string` | - | CSS classes for styling, supports render props. |
| `style` | `React.CSSProperties \| (values: DateFieldRenderProps) => React.CSSProperties` | - | Inline styles, supports render props. |
| `fullWidth` | `boolean` | `false` | Whether the date field should take full width of its container |
| `id` | `string` | - | The element's unique identifier. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
#### Value Props
| Prop | Type | Default | Description |
| ------------------ | ------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------- |
| `value` | `DateValue \| null` | - | Current value (controlled). Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `defaultValue` | `DateValue \| null` | - | Default value (uncontrolled). Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `onChange` | `(value: DateValue \| null) => void` | - | Handler called when the value changes. |
| `placeholderValue` | `DateValue \| null` | - | Placeholder date that influences the format of the placeholder. |
#### Validation Props
| Prop | Type | Default | Description |
| -------------------- | -------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `isRequired` | `boolean` | `false` | Whether user input is required before form submission. |
| `isInvalid` | `boolean` | - | Whether the value is invalid. |
| `minValue` | `DateValue \| null` | - | The minimum allowed date that a user may select. Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `maxValue` | `DateValue \| null` | - | The maximum allowed date that a user may select. Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `isDateUnavailable` | `(date: DateValue) => boolean` | - | Callback that is called for each date. If it returns true, the date is unavailable. |
| `validate` | `(value: DateValue) => ValidationError \| true \| null \| undefined` | - | Custom validation function. |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA attributes. |
#### Format Props
| Prop | Type | Default | Description |
| ------------------------- | ------------- | ------- | -------------------------------------------------------------------------------------------- |
| `granularity` | `Granularity` | - | Determines the smallest unit displayed. Defaults to `"day"` for dates, `"minute"` for times. |
| `hourCycle` | `12 \| 24` | - | Whether to display time in 12 or 24 hour format. By default, determined by locale. |
| `hideTimeZone` | `boolean` | `false` | Whether to hide the time zone abbreviation. |
| `shouldForceLeadingZeros` | `boolean` | - | Whether to always show leading zeros in month, day, and hour fields. |
#### State Props
| Prop | Type | Default | Description |
| ------------ | --------- | ------- | -------------------------------------------------- |
| `isDisabled` | `boolean` | - | Whether the input is disabled. |
| `isReadOnly` | `boolean` | - | Whether the input can be selected but not changed. |
#### Form Props
| Prop | Type | Default | Description |
| -------------- | --------- | ------- | -------------------------------------------------------------------------------- |
| `name` | `string` | - | Name of the input element, for HTML form submission. Submits as ISO 8601 string. |
| `autoFocus` | `boolean` | - | Whether the element should receive focus on render. |
| `autoComplete` | `string` | - | Type of autocomplete functionality the input should provide. |
#### Accessibility Props
| Prop | Type | Default | Description |
| ------------------ | -------- | ------- | ----------------------------------------------------- |
| `aria-label` | `string` | - | Accessibility label when no visible label is present. |
| `aria-labelledby` | `string` | - | ID of elements that label this field. |
| `aria-describedby` | `string` | - | ID of elements that describe this field. |
| `aria-details` | `string` | - | ID of elements with additional details. |
### Composition Components
DateField works with these separate components that should be imported and used directly:
* **Label** - Field label component from `@darkcode-ui/react`
* **DateField.Group** - Date input group component (documented below)
* **DateField.Input** - Input component with segmented editing from `@darkcode-ui/react`
* **DateField.InputContainer** - Scrollable container for grouping multiple inputs (e.g. start/end range inputs) with horizontal overflow
* **DateField.Segment** - Individual date segment (year, month, day, etc.)
* **DateField.Prefix** / **DateField.Suffix** - Prefix and suffix slots for the input group
* **Description** - Helper text component from `@darkcode-ui/react`
* **FieldError** - Validation error message from `@darkcode-ui/react`
Each of these components has its own props API. Use them directly within DateField for composition:
```tsx
import {parseDate} from '@internationalized/date';
import {DateField, Label, Description, FieldError} from '@darkcode-ui/react';
Appointment Date
{(segment) => }
Select a date from today onwards.
Please select a valid date.
```
### DateValue Types
DateField uses types from [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/):
* `CalendarDate` - Date without time or timezone
* `CalendarDateTime` - Date with time but no timezone
* `ZonedDateTime` - Date with time and timezone
* `Time` - Time only
Example:
```tsx
import {parseDate, today, getLocalTimeZone} from '@internationalized/date';
// Parse from string
const date = parseDate('2024-01-15');
// Today's date
const todayDate = today(getLocalTimeZone());
// Use in DateField
{/* ... */}
```
> **Note:** DateField uses the [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) package for date manipulation, parsing, and type definitions. See the [Internationalized Date documentation](https://react-aria.adobe.com/internationalized/date/) for more information about available types and functions.
### DateFieldRenderProps
When using render props with `className`, `style`, or `children`, these values are available:
| Prop | Type | Description |
| ---------------- | --------- | ----------------------------------------------- |
| `isDisabled` | `boolean` | Whether the field is disabled. |
| `isInvalid` | `boolean` | Whether the field is currently invalid. |
| `isReadOnly` | `boolean` | Whether the field is read-only. |
| `isRequired` | `boolean` | Whether the field is required. |
| `isFocused` | `boolean` | Whether the field is currently focused. |
| `isFocusWithin` | `boolean` | Whether any child element is focused. |
| `isFocusVisible` | `boolean` | Whether focus is visible (keyboard navigation). |
### DateField.Group Props
DateField.Group accepts all props from React Aria's `Group` component plus the following:
| Prop | Type | Default | Description |
| ----------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `fullWidth` | `boolean` | `false` | Whether the date input group should take full width of its container |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
### DateField.Input Props
DateField.Input accepts all props from React Aria's `DateInput` component plus the following:
| Prop | Type | Default | Description |
| ----------- | -------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the input. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
The `DateField.Input` component accepts a render prop function that receives date segments. Each segment represents a part of the date (year, month, day, etc.).
### DateField.Segment Props
DateField.Segment accepts all props from React Aria's `DateSegment` component:
| Prop | Type | Default | Description |
| ----------- | ------------- | ------- | ------------------------------------------------------------- |
| `segment` | `DateSegment` | - | The date segment object from the DateField.Input render prop. |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
### DateField.InputContainer Props
DateField.InputContainer accepts standard HTML `div` attributes:
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ----------------------------------------------------------------------------------------------------- |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `children` | `ReactNode` | - | Content to display inside the scrollable container (typically multiple `DateField.Input` components). |
### DateField.Prefix Props
DateField.Prefix accepts standard HTML `div` attributes:
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------------------------------------- |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `children` | `ReactNode` | - | Content to display in the prefix slot. |
### DateField.Suffix Props
DateField.Suffix accepts standard HTML `div` attributes:
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------------------------------------- |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `children` | `ReactNode` | - | Content to display in the suffix slot. |
## DateField.Group Styling
### Customizing the component classes
The base classes power every instance. Override them once with `@layer components`.
```css
@layer components {
.date-input-group {
@apply inline-flex h-9 items-center overflow-hidden rounded-field border bg-field text-sm text-field-foreground shadow-field outline-none;
&:hover,
&[data-hovered="true"] {
@apply bg-field-hover;
}
&[data-focus-within="true"],
&:focus-within {
@apply status-focused-field;
}
&[data-invalid="true"] {
@apply status-invalid-field;
}
&[data-disabled="true"],
&[aria-disabled="true"] {
@apply status-disabled;
}
}
.date-input-group__input {
@apply flex flex-1 items-center gap-px rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
}
.date-input-group__segment {
@apply inline-block rounded-md px-0.5 text-end tabular-nums outline-none;
&:focus,
&[data-focused="true"] {
@apply bg-accent-soft text-accent-soft-foreground;
}
}
.date-input-group__input-container {
@apply flex flex-1 items-center;
overflow-x: auto;
overflow-y: clip;
scrollbar-width: none;
}
.date-input-group__prefix,
.date-input-group__suffix {
@apply pointer-events-none shrink-0 text-field-placeholder flex items-center;
}
}
```
### DateField.Group CSS Classes
* `.date-input-group` – Root container styling
* `.date-input-group__input` – Input wrapper styling
* `.date-input-group__input-container` – Scrollable container for grouping multiple inputs
* `.date-input-group__segment` – Individual date segment styling
* `.date-input-group__prefix` – Prefix element styling
* `.date-input-group__suffix` – Suffix element styling
### DateField.Group Interactive States
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Focus Within**: `[data-focus-within="true"]` or `:focus-within`
* **Invalid**: `[data-invalid="true"]` (also syncs with `aria-invalid`)
* **Disabled**: `[data-disabled="true"]` or `[aria-disabled="true"]`
* **Segment Focus**: `:focus` or `[data-focused="true"]` on segment elements
* **Segment Placeholder**: `[data-placeholder="true"]` on segment elements
# DatePicker
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/date-picker
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/date-picker.mdx
> Composable date picker built on React Aria DatePicker with DateField and Calendar composition
## Import
```tsx
import { DatePicker, DateField, Calendar, Label } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@darkcode-ui/react";
export function Basic() {
return (
Date
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Anatomy
`DatePicker` follows a composition-first API. Compose `DateField` and `Calendar` explicitly to control structure and styling.
```tsx
import {Calendar, DateField, DatePicker, Label} from '@darkcode-ui/react';
export default () => (
{(segment) => }
{(day) => {day} }
{(date) => }
)
```
### Controlled
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Calendar, DateField, DatePicker, Description, Label} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(today(getLocalTimeZone()));
return (
Date
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
Current value: {value ? value.toString() : "(empty)"}
setValue(today(getLocalTimeZone()))}>
Set today
setValue(null)}>
Clear
);
}
```
### Validation
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar, DateField, DatePicker, FieldError, Label} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function WithValidation() {
const [value, setValue] = useState(null);
const currentDate = today(getLocalTimeZone());
const isInvalid = value != null && value.compare(currentDate) < 0;
return (
Appointment date
{(segment) => }
Date must be today or in the future.
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Format Options
Control how DatePicker values are displayed with props such as `granularity`, `hourCycle`, `hideTimeZone`, and `shouldForceLeadingZeros`.
```tsx
"use client";
import type {TimeValue} from "@darkcode-ui/react";
import type {DateValue} from "@internationalized/date";
import {
Calendar,
DateField,
DatePicker,
Label,
ListBox,
Select,
Switch,
TimeField,
} from "@darkcode-ui/react";
import {getLocalTimeZone, parseDate, parseZonedDateTime} from "@internationalized/date";
import {useMemo, useState} from "react";
type Granularity = "day" | "hour" | "minute" | "second";
type HourCycle = 12 | 24;
const granularityOptions: {label: string; value: Granularity}[] = [
{label: "Day", value: "day"},
{label: "Hour", value: "hour"},
{label: "Minute", value: "minute"},
{label: "Second", value: "second"},
];
const hourCycleOptions: {label: string; value: HourCycle}[] = [
{label: "12-hour", value: 12},
{label: "24-hour", value: 24},
];
export function FormatOptions() {
const [granularity, setGranularity] = useState("minute");
const [hourCycle, setHourCycle] = useState(12);
const [hideTimeZone, setHideTimeZone] = useState(false);
const [shouldForceLeadingZeros, setShouldForceLeadingZeros] = useState(false);
const timeGranularity = granularity !== "day" ? granularity : undefined;
const showTimeField = !!timeGranularity;
const defaultValue = useMemo(() => {
const localTimeZone = getLocalTimeZone();
if (granularity === "day") {
return parseDate("2026-02-03");
}
return parseZonedDateTime(`2026-02-03T08:45:00[${localTimeZone}]`);
}, [granularity]);
return (
{({state}) => (
<>
Date and time
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
{!!showTimeField && (
Time
state.setTimeValue(v as TimeValue)}
>
{(segment) => }
)}
>
)}
setGranularity(value as Granularity)}
>
Granularity
{granularityOptions.map((option) => (
{option.label}
))}
setHourCycle(Number(value) as HourCycle)}
>
Hour cycle
{hourCycleOptions.map((option) => (
{option.label}
))}
Hide timezone
Force leading zeros
);
}
```
### Disabled
```tsx
"use client";
import {Calendar, DateField, DatePicker, Description, Label} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
return (
Date
{(segment) => }
This date picker is disabled.
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Custom Indicator
`DatePicker.TriggerIndicator` renders the default `IconCalendar` when no children are provided. Pass children to replace it.
```tsx
"use client";
import {Calendar, DateField, DatePicker, Description, Label} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function WithCustomIndicator() {
return (
Date
{(segment) => }
Replace the default calendar icon by passing custom children.
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Form Example
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {
Button,
Calendar,
DateField,
DatePicker,
Description,
FieldError,
Form,
Label,
} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function FormExample() {
const [value, setValue] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const currentDate = today(getLocalTimeZone());
const isInvalid = value != null && value.compare(currentDate) < 0;
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
setTimeout(() => {
setValue(null);
setIsSubmitting(false);
}, 1200);
};
return (
);
}
```
### International Calendar
By default, DatePicker displays dates using the calendar system for the user's locale. You can override this by wrapping your DatePicker with `I18nProvider` and setting the [Unicode calendar locale extension](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string).
The example below shows the Indian calendar system:
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
return (
Event date
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**Note:** The `onChange` event always returns a date in the same calendar system as the `value` or `defaultValue` (Gregorian if no value is provided), regardless of the displayed locale. This ensures your application logic works consistently with a single calendar system while still displaying dates in the user's preferred format.
For a complete list of supported calendar systems and their identifiers, see:
* [React Aria Calendar Implementations](https://react-aria.adobe.com/internationalized/date/Calendar#implementations)
* [React Aria International Calendars](https://react-aria.adobe.com/Calendar#international-calendars)
### Custom Render Function
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}
>
}>Date
}
>
}>
{(segment) => (
}
segment={segment}
/>
)}
}
>
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
## Related Components
* **Calendar**: Interactive month grid for selecting dates
* **RangeCalendar**: Interactive month grid for selecting date ranges
* **DateField**: Date input field with labels, descriptions, and validation
## Styling
### Passing Tailwind CSS classes
You can style each composition part independently:
```tsx
import {Calendar, DateField, DatePicker, Label} from '@darkcode-ui/react';
function CustomDatePicker() {
return (
Date
{(segment) => }
{/* Calendar parts */}
);
}
```
### Customizing the component classes
To customize DatePicker base classes, use `@layer components`.
```css
@layer components {
.date-picker {
@apply inline-flex flex-col gap-1;
}
.date-picker__trigger {
@apply inline-flex items-center justify-between;
}
.date-picker__trigger-indicator {
@apply text-muted;
}
.date-picker__popover {
@apply min-w-[var(--trigger-width)] p-0;
}
}
```
DarkCode UI follows [BEM](https://getbem.com/) naming for reusable customization.
### CSS Classes
DatePicker uses these classes in `packages/styles/components/date-picker.css`:
* `.date-picker` - Root wrapper.
* `.date-picker__trigger` - Trigger part that opens the popover.
* `.date-picker__trigger-indicator` - Default/custom indicator slot.
* `.date-picker__popover` - Popover content wrapper.
### Interactive States
DatePicker supports React Aria data attributes and pseudo states:
* **Open**: `[data-open="true"]` on trigger.
* **Disabled**: `[data-disabled="true"]` or `[aria-disabled="true"]` on trigger.
* **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]` on trigger.
* **Hover**: `:hover` or `[data-hovered="true"]` on trigger.
## API Reference
### DatePicker Props
DatePicker inherits all props from React Aria [DatePicker](https://react-aria.adobe.com/DatePicker.md).
| Prop | Type | Default | Description |
| -------------- | ----------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `value` | `DateValue \| null` | - | Controlled selected date value. |
| `defaultValue` | `DateValue \| null` | - | Default selected value in uncontrolled mode. |
| `onChange` | `(value: DateValue \| null) => void` | - | Called when selected date changes. |
| `isOpen` | `boolean` | - | Controlled popover open state. |
| `defaultOpen` | `boolean` | `false` | Initial popover open state. |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Called when popover open state changes. |
| `isDisabled` | `boolean` | `false` | Disables date selection and trigger interactions. |
| `isInvalid` | `boolean` | - | Marks the field as invalid for validation state. |
| `minValue` | `DateValue` | - | Minimum selectable date. |
| `maxValue` | `DateValue` | - | Maximum selectable date. |
| `name` | `string` | - | Name used for HTML form submission. |
| `children` | `ReactNode \| (values: DatePickerRenderProps) => ReactNode` | - | Composed content or render function. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Composition Parts
| Component | Description |
| ----------------------------- | ----------------------------------------------------------- |
| `DatePicker.Root` | Root date picker container and state owner. |
| `DatePicker.Trigger` | Trigger button, usually rendered inside `DateField.Suffix`. |
| `DatePicker.TriggerIndicator` | Indicator slot with default calendar icon. |
| `DatePicker.Popover` | Popover wrapper for `Calendar` content. |
### Related packages
* [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — date types (`CalendarDate`, `CalendarDateTime`, `ZonedDateTime`) and utilities used by all date components
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree
* [`useLocale`](https://react-aria.adobe.com/useLocale) — read the current locale and layout direction
# DateRangePicker
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/date-range-picker
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/date-range-picker.mdx
> Composable date range picker built on React Aria DateRangePicker with DateField and RangeCalendar composition
## Import
```tsx
import { DateField, DateRangePicker, Label, RangeCalendar } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@darkcode-ui/react";
export function Basic() {
return (
Trip dates
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Anatomy
`DateRangePicker` follows a composition-first API. Compose `DateField` and `RangeCalendar` explicitly to control structure and styling.
```tsx
import {DateField, DateRangePicker, Label, RangeCalendar} from '@darkcode-ui/react';
export default () => (
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
)
```
### Controlled
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {
Button,
DateField,
DateRangePicker,
Description,
Label,
RangeCalendar,
} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
type DateRange = {
start: DateValue;
end: DateValue;
};
export function Controlled() {
const start = today(getLocalTimeZone());
const [value, setValue] = useState({end: start.add({days: 4}), start});
return (
Trip dates
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
Current value: {value ? `${value.start.toString()} -> ${value.end.toString()}` : "(empty)"}
{
const nextStart = today(getLocalTimeZone());
setValue({end: nextStart.add({days: 6}), start: nextStart});
}}
>
Set week
setValue(null)}>
Clear
);
}
```
### Validation
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {DateField, DateRangePicker, FieldError, Label, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
type DateRange = {
start: DateValue;
end: DateValue;
};
export function WithValidation() {
const [value, setValue] = useState(null);
const currentDate = today(getLocalTimeZone());
const isInvalid =
value != null && (value.start.compare(currentDate) < 0 || value.end.compare(value.start) < 0);
return (
Booking period
{(segment) => }
{(segment) => }
Select a valid range starting today or later.
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Format Options
Control how DateRangePicker values are displayed with props such as `granularity`, `hourCycle`, `hideTimeZone`, and `shouldForceLeadingZeros`.
```tsx
"use client";
import type {TimeValue} from "@darkcode-ui/react";
import type {DateValue} from "@internationalized/date";
import {
DateField,
DateRangePicker,
Label,
ListBox,
RangeCalendar,
Select,
Separator,
Switch,
TimeField,
useLocale,
} from "@darkcode-ui/react";
import {
DateFormatter,
getLocalTimeZone,
parseDate,
parseZonedDateTime,
} from "@internationalized/date";
import {useMemo, useState} from "react";
type Granularity = "day" | "hour" | "minute" | "second";
type HourCycle = 12 | 24;
type DateRange = {
start: DateValue;
end: DateValue;
};
const granularityOptions: {label: string; value: Granularity}[] = [
{label: "Day", value: "day"},
{label: "Hour", value: "hour"},
{label: "Minute", value: "minute"},
{label: "Second", value: "second"},
];
const hourCycleOptions: {label: string; value: HourCycle}[] = [
{label: "12-hour", value: 12},
{label: "24-hour", value: 24},
];
export function FormatOptions() {
const [granularity, setGranularity] = useState("minute");
const [hourCycle, setHourCycle] = useState(12);
const [hideTimeZone, setHideTimeZone] = useState(false);
const [shouldForceLeadingZeros, setShouldForceLeadingZeros] = useState(false);
const {locale} = useLocale();
const dateFormatter = new DateFormatter(locale, {
day: "numeric",
month: "short",
year: "numeric",
});
const formatDate = (date: DateRange) => {
const localTimeZone = getLocalTimeZone();
const start = date.start.toDate(localTimeZone);
const end = date.end.toDate(localTimeZone);
return dateFormatter.formatRange(start, end);
};
const defaultValue = useMemo(() => {
const localTimeZone = getLocalTimeZone();
if (granularity === "day") {
return {
end: parseDate("2025-02-10"),
start: parseDate("2025-02-03"),
};
}
return {
end: parseZonedDateTime(`2026-02-10T18:45:00[${localTimeZone}]`),
start: parseZonedDateTime(`2026-02-03T08:45:00[${localTimeZone}]`),
};
}, [granularity]);
const timeGranularity = granularity !== "day" ? granularity : undefined;
const showTimeField = !!timeGranularity;
return (
{({state}) => (
<>
Date range
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
{!!showTimeField && (
Start Time
state.setTimeRange({
end: state.timeRange?.end as TimeValue,
start: v as TimeValue,
})
}
>
{(segment) => }
End Time
state.setTimeRange({
end: v as TimeValue,
start: state.timeRange?.start as TimeValue,
})
}
>
{(segment) => }
)}
Selected:{" "}
{state.value && state.value.start && state.value.end
? formatDate({end: state.value.end, start: state.value.start})
: "No date selected"}
>
)}
Format Options
setGranularity(value as Granularity)}
>
Granularity
{granularityOptions.map((option) => (
{option.label}
))}
setHourCycle(Number(value) as HourCycle)}
>
Hour cycle
{hourCycleOptions.map((option) => (
{option.label}
))}
Hide timezone
Force leading zeros
);
}
```
### Disabled
```tsx
"use client";
import {DateField, DateRangePicker, Description, Label, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
const start = today(getLocalTimeZone());
return (
Trip dates
{(segment) => }
{(segment) => }
This date range picker is disabled.
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Custom Indicator
`DateRangePicker.TriggerIndicator` renders the default `IconCalendar` when no children are provided. Pass children to replace it.
```tsx
"use client";
import {DateField, DateRangePicker, Description, Label, RangeCalendar} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function WithCustomIndicator() {
return (
Trip dates
{(segment) => }
{(segment) => }
Replace the default calendar icon by passing custom children.
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Form Example
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {
Button,
DateField,
DateRangePicker,
Description,
FieldError,
Form,
Label,
RangeCalendar,
} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
type DateRange = {
start: DateValue;
end: DateValue;
};
export function FormExample() {
const [value, setValue] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const currentDate = today(getLocalTimeZone());
const isInvalid =
value != null && (value.start.compare(currentDate) < 0 || value.end.compare(value.start) < 0);
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (!value || isInvalid) return;
setIsSubmitting(true);
setTimeout(() => {
setValue(null);
setIsSubmitting(false);
}, 1200);
};
return (
);
}
```
### International Calendar
By default, DateRangePicker displays dates using the calendar system for the user's locale. You can override this by wrapping your DateRangePicker with `I18nProvider` and setting the [Unicode calendar locale extension](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string).
The example below shows the Indian calendar system:
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
const start = today(getLocalTimeZone());
return (
Trip dates
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**Note:** The `onChange` event always returns dates in the same calendar system as the `value` or `defaultValue` (Gregorian if no value is provided), regardless of the displayed locale.
For a complete list of supported calendar systems and their identifiers, see:
* [React Aria Calendar Implementations](https://react-aria.adobe.com/internationalized/date/Calendar#implementations)
* [React Aria International Calendars](https://react-aria.adobe.com/Calendar#international-calendars)
### Custom Render Function
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}
startName="startDate"
>
Trip dates
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
## Related Components
* **RangeCalendar**: Interactive month grid for selecting date ranges
* **Calendar**: Interactive month grid for selecting dates
* **DateField**: Date input field with labels, descriptions, and validation
## Styling
### Passing Tailwind CSS classes
You can style each composition part independently:
```tsx
import {DateField, DateRangePicker, Label, RangeCalendar} from '@darkcode-ui/react';
function CustomDateRangePicker() {
return (
Trip dates
{(segment) => }
{(segment) => }
{/* RangeCalendar parts */}
);
}
```
### Customizing the component classes
To customize DateRangePicker base classes, use `@layer components`.
```css
@layer components {
.date-range-picker {
@apply inline-flex flex-col gap-1;
}
.date-range-picker__trigger {
@apply inline-flex items-center justify-between;
}
.date-range-picker__trigger-indicator {
@apply text-muted;
}
.date-range-picker__range-separator {
@apply px-2 text-default;
}
.date-range-picker__popover {
@apply min-w-[var(--trigger-width)] p-0;
}
}
```
DarkCode UI follows [BEM](https://getbem.com/) naming for reusable customization.
### CSS Classes
DateRangePicker uses these classes in `packages/styles/components/date-range-picker.css`:
* `.date-range-picker` - Root wrapper.
* `.date-range-picker__trigger` - Trigger part that opens the popover.
* `.date-range-picker__trigger-indicator` - Default/custom indicator slot.
* `.date-range-picker__range-separator` - Separator between start and end date inputs.
* `.date-range-picker__popover` - Popover content wrapper.
### Interactive States
DateRangePicker supports React Aria data attributes and pseudo states:
* **Open**: `[data-open="true"]` on trigger.
* **Disabled**: `[data-disabled="true"]` or `[aria-disabled="true"]` on trigger.
* **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]` on trigger.
* **Hover**: `:hover` or `[data-hovered="true"]` on trigger.
## API Reference
### DateRangePicker Props
DateRangePicker inherits all props from React Aria [DateRangePicker](https://react-aria.adobe.com/DateRangePicker).
| Prop | Type | Default | Description |
| -------------- | ---------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `value` | `{ start: DateValue; end: DateValue } \| null` | - | Controlled selected date range value. |
| `defaultValue` | `{ start: DateValue; end: DateValue } \| null` | - | Default selected range in uncontrolled mode. |
| `onChange` | `(value: { start: DateValue; end: DateValue } \| null) => void` | - | Called when selected range changes. |
| `isOpen` | `boolean` | - | Controlled popover open state. |
| `defaultOpen` | `boolean` | `false` | Initial popover open state. |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Called when popover open state changes. |
| `isDisabled` | `boolean` | `false` | Disables range selection and trigger interactions. |
| `isInvalid` | `boolean` | - | Marks the field as invalid for validation state. |
| `minValue` | `DateValue` | - | Minimum selectable date. |
| `maxValue` | `DateValue` | - | Maximum selectable date. |
| `startName` | `string` | - | Name used for the start date in HTML form submission. |
| `endName` | `string` | - | Name used for the end date in HTML form submission. |
| `children` | `ReactNode \| (values: DateRangePickerRenderProps) => ReactNode` | - | Composed content or render function. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Composition Parts
| Component | Description |
| ---------------------------------- | ----------------------------------------------------------- |
| `DateRangePicker.Root` | Root date range picker container and state owner. |
| `DateRangePicker.Trigger` | Trigger button, usually rendered inside `DateField.Suffix`. |
| `DateRangePicker.TriggerIndicator` | Indicator slot with default calendar icon. |
| `DateRangePicker.RangeSeparator` | Separator part between start and end date inputs. |
| `DateRangePicker.Popover` | Popover wrapper for `RangeCalendar` content. |
### Related packages
* [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — date types (`CalendarDate`, `CalendarDateTime`, `ZonedDateTime`) and utilities used by all date components
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree
* [`useLocale`](https://react-aria.adobe.com/useLocale) — read the current locale and layout direction
# RangeCalendar
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/range-calendar
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/range-calendar.mdx
> Composable date range picker with month grid, navigation, and year picker support built on React Aria RangeCalendar
## Import
```tsx
import { RangeCalendar } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
export function Basic() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Anatomy
```tsx
import {RangeCalendar} from '@darkcode-ui/react';
export default () => (
{(day) => {day} }
{(date) => }
)
```
### Year Picker
`RangeCalendar.YearPickerTrigger`, `RangeCalendar.YearPickerGrid`, and their body/cell subcomponents provide an integrated year navigation pattern.
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
export function YearPicker() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Default Value
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
import {parseDate} from "@internationalized/date";
export function DefaultValue() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Controlled
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, ButtonGroup, Description, RangeCalendar} from "@darkcode-ui/react";
import {
getLocalTimeZone,
parseDate,
startOfMonth,
startOfWeek,
today,
} from "@internationalized/date";
import {useState} from "react";
import {useLocale} from "react-aria-components";
type DateRange = {
start: DateValue;
end: DateValue;
};
export function Controlled() {
const [value, setValue] = useState(null);
const [focusedDate, setFocusedDate] = useState(parseDate("2025-12-25"));
const {locale} = useLocale();
return (
{
const start = today(getLocalTimeZone());
setFocusedDate(start);
}}
>
This week
{
const nextWeekStart = startOfWeek(today(getLocalTimeZone()).add({weeks: 1}), locale);
setFocusedDate(nextWeekStart);
}}
>
Next week
{
const nextMonthStart = startOfMonth(today(getLocalTimeZone()).add({months: 1}));
setFocusedDate(nextMonthStart);
}}
>
Next month
{(day) => {day} }
{(date) => }
Selected range: {value ? `${value.start.toString()} -> ${value.end.toString()}` : "(none)"}
{
const start = today(getLocalTimeZone());
setValue({end: start.add({days: 6}), start});
setFocusedDate(start);
}}
>
Set 1 week
{
const start = parseDate("2025-12-20");
setValue({end: parseDate("2025-12-31"), start});
setFocusedDate(start);
}}
>
Set Holidays
setValue(null)}>
Clear
);
}
```
### Min and Max Dates
```tsx
"use client";
import {Description, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function MinMaxDates() {
const now = today(getLocalTimeZone());
const minDate = now;
const maxDate = now.add({months: 3});
return (
{(day) => {day} }
{(date) => }
Select dates between today and {maxDate.toString()}
);
}
```
### Unavailable Dates
Use `isDateUnavailable` to block dates such as weekends, holidays, or booked slots.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function UnavailableDates() {
const now = today(getLocalTimeZone());
const blockedRanges = [
[now.add({days: 2}), now.add({days: 5})],
[now.add({days: 12}), now.add({days: 13})],
] as const;
const isDateUnavailable = (date: DateValue) => {
return blockedRanges.some(([start, end]) => date.compare(start) >= 0 && date.compare(end) <= 0);
};
return (
{(day) => {day} }
{(date) => }
Some days are unavailable
);
}
```
### Allows Non-Contiguous Ranges
Enable `allowsNonContiguousRanges` to allow selection across unavailable dates.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function AllowsNonContiguousRanges() {
const now = today(getLocalTimeZone());
const blockedRanges = [
[now.add({days: 2}), now.add({days: 5})],
[now.add({days: 12}), now.add({days: 13})],
] as const;
const isDateUnavailable = (date: DateValue) => {
return blockedRanges.some(([start, end]) => date.compare(start) >= 0 && date.compare(end) <= 0);
};
return (
{(day) => {day} }
{(date) => }
Non-contiguous ranges are allowed across unavailable dates
);
}
```
### Disabled
```tsx
"use client";
import {Description, RangeCalendar} from "@darkcode-ui/react";
export function Disabled() {
return (
{(day) => {day} }
{(date) => }
Range calendar is disabled
);
}
```
### Read Only
```tsx
"use client";
import {Description, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function ReadOnly() {
return (
{(day) => {day} }
{(date) => }
Range calendar is read-only
);
}
```
### Invalid
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
type DateRange = {
start: DateValue;
end: DateValue;
};
export function Invalid() {
const now = today(getLocalTimeZone());
const [value, setValue] = useState({
end: now.add({days: 14}),
start: now.add({days: 6}),
});
const isInvalid = value.end.compare(value.start) > 7;
return (
{(day) => {day} }
{(date) => }
{isInvalid ? (
Maximum stay duration is 1 week
) : (
Select a stay of up to 7 days
)}
);
}
```
### Focused Value
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Description, RangeCalendar} from "@darkcode-ui/react";
import {parseDate} from "@internationalized/date";
import {useState} from "react";
export function FocusedValue() {
const [focusedDate, setFocusedDate] = useState(parseDate("2025-06-15"));
return (
{(day) => {day} }
{(date) => }
Focused: {focusedDate.toString()}
setFocusedDate(parseDate("2025-01-01"))}
>
Go to Jan
setFocusedDate(parseDate("2025-06-15"))}
>
Go to Jun
setFocusedDate(parseDate("2025-12-25"))}
>
Go to Christmas
);
}
```
### Cell Indicators
You can customize `RangeCalendar.Cell` children and use `RangeCalendar.CellIndicator` to display metadata like events.
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, isToday} from "@internationalized/date";
const datesWithEvents = [3, 7, 12, 15, 21, 28];
export function WithIndicators() {
return (
{(day) => {day} }
{(date) => (
{({formattedDate}) => (
<>
{formattedDate}
{(isToday(date, getLocalTimeZone()) || datesWithEvents.includes(date.day)) && (
)}
>
)}
)}
);
}
```
### Multiple Months
Render multiple grids with `visibleDuration` and `offset` for booking and planning experiences.
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone} from "@internationalized/date";
import React from "react";
import {RangeCalendarStateContext, useLocale} from "react-aria-components";
function RangeCalendarMonthHeading({offset = 0}: {offset?: number}) {
const state = React.useContext(RangeCalendarStateContext)!;
const {locale} = useLocale();
const startDate = state.visibleRange.start;
const monthDate = startDate.add({months: offset});
const dateObj = monthDate.toDate(getLocalTimeZone());
const monthYear = new Intl.DateTimeFormat(locale, {month: "long", year: "numeric"}).format(
dateObj,
);
return {monthYear} ;
}
export function MultipleMonths() {
return (
{(day) => {day} }
{(date) => }
{(day) => {day} }
{(date) => }
);
}
```
### Week View
Select a range across a fixed number of weeks by passing `visibleDuration={{weeks: n}}`. The grid keeps its seven columns.
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
export function WeekView() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Day View
Select a range within a horizontal strip of days by passing `visibleDuration={{days: n}}`. The column count follows the visible day span (capped at seven).
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
export function DayView() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### International Calendars
By default, RangeCalendar displays dates using the calendar system for the user's locale. You can override this by wrapping your RangeCalendar with `I18nProvider` and setting the [Unicode calendar locale extension](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string).
The example below shows the Indian calendar system:
```tsx
"use client";
import {RangeCalendar} from "@darkcode-ui/react";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**Note:** The `onChange` event always returns a date in the same calendar system as the `value` or `defaultValue` (Gregorian if no value is provided), regardless of the displayed locale.
### Real-World Example
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, RangeCalendar} from "@darkcode-ui/react";
import {getLocalTimeZone, isWeekend, today} from "@internationalized/date";
import {useState} from "react";
import {useLocale} from "react-aria-components";
type DateRange = {
start: DateValue;
end: DateValue;
};
export function BookingCalendar() {
const [selectedRange, setSelectedRange] = useState(null);
const {locale} = useLocale();
const blockedDates = [5, 6, 12, 13, 14, 20];
const isDateUnavailable = (date: DateValue) => {
return isWeekend(date, locale) || blockedDates.includes(date.day);
};
return (
{(day) => {day} }
{(date) => (
{({formattedDate, isUnavailable}) => (
<>
{formattedDate}
{!isUnavailable &&
!isWeekend(date, locale) &&
blockedDates.includes(date.day) && }
>
)}
)}
Blocked dates
Weekend/Unavailable
{selectedRange ? (
Book {selectedRange.start.toString()} -> {selectedRange.end.toString()}
) : null}
);
}
```
## Related Components
* **Calendar**: Interactive month grid for selecting dates
* **DateField**: Date input field with labels, descriptions, and validation
* **DatePicker**: Composable date picker with date field trigger and calendar popover
## Styling
### Passing Tailwind CSS classes
```tsx
import {RangeCalendar} from '@darkcode-ui/react';
function CustomRangeCalendar() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Customizing the component classes
```css
@layer components {
.range-calendar {
@apply w-80 rounded-2xl border border-border bg-surface p-3 shadow-sm;
}
.range-calendar__heading {
@apply text-sm font-semibold text-default;
}
.range-calendar__cell[data-selected="true"] .range-calendar__cell-button {
@apply bg-accent text-accent-foreground;
}
}
```
### CSS Classes
RangeCalendar uses these classes in `packages/styles/components/range-calendar.css` and `packages/styles/components/calendar-year-picker.css`:
* `.range-calendar` - Root container.
* `.range-calendar__header` - Header row containing nav buttons and heading.
* `.range-calendar__heading` - Current month label.
* `.range-calendar__nav-button` - Previous/next navigation controls.
* `.range-calendar__grid` - Main day grid.
* `.range-calendar__grid-header` - Weekday header row wrapper.
* `.range-calendar__grid-body` - Date rows wrapper.
* `.range-calendar__header-cell` - Weekday header cell.
* `.range-calendar__cell` - Interactive day cell wrapper.
* `.range-calendar__cell-button` - Interactive day button inside each cell.
* `.range-calendar__cell-indicator` - Dot indicator inside a day cell.
* `.calendar-year-picker__trigger` - Year picker toggle button.
* `.calendar-year-picker__trigger-heading` - Heading text inside year picker trigger.
* `.calendar-year-picker__trigger-indicator` - Indicator icon inside year picker trigger.
* `.calendar-year-picker__year-grid` - Overlay grid of selectable years.
* `.calendar-year-picker__year-cell` - Individual year option.
### Interactive States
RangeCalendar supports both pseudo-classes and React Aria data attributes:
* **Selected**: `[data-selected="true"]`
* **Selection start**: `[data-selection-start="true"]`
* **Selection end**: `[data-selection-end="true"]`
* **Range middle**: `[data-selection-in-range="true"]`
* **Today**: `[data-today="true"]`
* **Unavailable**: `[data-unavailable="true"]`
* **Outside month**: `[data-outside-month="true"]`
* **Hovered**: `:hover` or `[data-hovered="true"]`
* **Pressed**: `:active` or `[data-pressed="true"]`
* **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]`
* **Disabled**: `:disabled` or `[data-disabled="true"]`
## API Reference
### RangeCalendar Props
RangeCalendar inherits all props from React Aria [RangeCalendar](https://react-spectrum.adobe.com/react-aria/RangeCalendar.html).
| Prop | Type | Default | Description |
| --------------------------- | ---------------------------------------- | --------------------------- | ------------------------------------------------------ |
| `value` | `RangeValue \| null` | - | Controlled selected range. |
| `defaultValue` | `RangeValue \| null` | - | Initial selected range (uncontrolled). |
| `onChange` | `(value: RangeValue) => void` | - | Called when selection changes. |
| `focusedValue` | `DateValue` | - | Controlled focused date. |
| `onFocusChange` | `(value: DateValue) => void` | - | Called when focus moves to another date. |
| `minValue` | `DateValue` | Calendar-aware `1900-01-01` | Earliest selectable date. |
| `maxValue` | `DateValue` | Calendar-aware `2099-12-31` | Latest selectable date. |
| `isDateUnavailable` | `(date: DateValue) => boolean` | - | Marks dates as unavailable. |
| `allowsNonContiguousRanges` | `boolean` | `false` | Allows ranges that span unavailable dates. |
| `isDisabled` | `boolean` | `false` | Disables interaction and selection. |
| `isReadOnly` | `boolean` | `false` | Keeps content readable but prevents selection changes. |
| `isInvalid` | `boolean` | `false` | Marks the calendar as invalid for validation UI. |
| `visibleDuration` | `{months?: number}` | `{months: 1}` | Number of visible months. |
| `defaultYearPickerOpen` | `boolean` | `false` | Initial open state of internal year picker. |
| `isYearPickerOpen` | `boolean` | - | Controlled year picker open state. |
| `onYearPickerOpenChange` | `(isOpen: boolean) => void` | - | Called when year picker open state changes. |
### Composition Parts
| Component | Description |
| ------------------------------------------ | ---------------------------------------------------------------------- |
| `RangeCalendar.Header` | Header container for navigation and heading. |
| `RangeCalendar.Heading` | Current month/year heading. |
| `RangeCalendar.NavButton` | Previous/next navigation control (`slot="previous"` or `slot="next"`). |
| `RangeCalendar.Grid` | Day grid for one month (`offset` supported for multi-month layouts). |
| `RangeCalendar.GridHeader` | Weekday header container. |
| `RangeCalendar.GridBody` | Date cell body container. |
| `RangeCalendar.HeaderCell` | Weekday label cell. |
| `RangeCalendar.Cell` | Individual date cell. |
| `RangeCalendar.CellIndicator` | Optional indicator element for custom metadata. |
| `RangeCalendar.YearPickerTrigger` | Trigger to toggle year-picker mode. |
| `RangeCalendar.YearPickerTriggerHeading` | Localized heading content inside the year-picker trigger. |
| `RangeCalendar.YearPickerTriggerIndicator` | Toggle icon inside the year-picker trigger. |
| `RangeCalendar.YearPickerGrid` | Overlay year selection grid container. |
| `RangeCalendar.YearPickerGridBody` | Body renderer for year grid cells. |
| `RangeCalendar.YearPickerCell` | Individual year option cell. |
### RangeCalendar.Cell Render Props
When `RangeCalendar.Cell` children is a function, React Aria render props are available:
| Prop | Type | Description |
| ------------------ | --------- | ---------------------------------------------------- |
| `formattedDate` | `string` | Localized day label for the cell. |
| `isSelected` | `boolean` | Whether the date is selected. |
| `isSelectionStart` | `boolean` | Whether the date is the start of the selected range. |
| `isSelectionEnd` | `boolean` | Whether the date is the end of the selected range. |
| `isUnavailable` | `boolean` | Whether the date is unavailable. |
| `isDisabled` | `boolean` | Whether the cell is disabled. |
| `isOutsideMonth` | `boolean` | Whether the date belongs to adjacent month. |
For a complete list of supported calendar systems and their identifiers, see:
* [React Aria Calendar Implementations](https://react-aria.adobe.com/internationalized/date/Calendar#implementations)
* [React Aria International Calendars](https://react-aria.adobe.com/Calendar#international-calendars)
### Related packages
* [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — date types (`CalendarDate`, `CalendarDateTime`, `ZonedDateTime`) and utilities used by all date components
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree
* [`useLocale`](https://react-aria.adobe.com/useLocale) — read the current locale and layout direction
# TimeField
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/time-field
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/time-field.mdx
> Time input field with labels, descriptions, and validation built on React Aria TimeField
## Import
```tsx
import { TimeField } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {Label, TimeField} from "@darkcode-ui/react";
export function Basic() {
return (
Time
{(segment) => }
);
}
```
### Anatomy
```tsx
import {TimeField, Label, Description, FieldError} from '@darkcode-ui/react';
export default () => (
{(segment) => }
)
```
> **TimeField** combines label, time input, description, and error into a single accessible component.
### With Description
```tsx
"use client";
import {Description, Label, TimeField} from "@darkcode-ui/react";
export function WithDescription() {
return (
Start time
{(segment) => }
Enter the start time
End time
{(segment) => }
Enter the end time
);
}
```
### Required Field
```tsx
"use client";
import {Description, Label, TimeField} from "@darkcode-ui/react";
export function Required() {
return (
Time
{(segment) => }
Appointment time
{(segment) => }
Required field
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
"use client";
import {FieldError, Label, TimeField} from "@darkcode-ui/react";
export function Invalid() {
return (
Time
{(segment) => }
Please enter a valid time
Time
{(segment) => }
Time must be within business hours
);
}
```
### With Validation
TimeField supports validation with `minValue`, `maxValue`, and custom validation logic.
```tsx
"use client";
import type {Time} from "@internationalized/date";
import {Description, FieldError, Label, TimeField} from "@darkcode-ui/react";
import {parseTime} from "@internationalized/date";
import {useState} from "react";
export function WithValidation() {
const [value, setValue] = useState(null);
const minTime = parseTime("09:00");
const maxTime = parseTime("17:00");
const isInvalid = value !== null && (value.compare(minTime) < 0 || value.compare(maxTime) > 0);
return (
Time
{(segment) => }
{isInvalid ? (
Time must be between 9:00 AM and 5:00 PM
) : (
Enter a time between 9:00 AM and 5:00 PM
)}
);
}
```
### Controlled
Control the value to synchronize with other components or state management.
```tsx
"use client";
import type {TimeValue} from "@darkcode-ui/react";
import {Button, Description, Label, TimeField} from "@darkcode-ui/react";
import {Time, getLocalTimeZone, now} from "@internationalized/date";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(null);
return (
Time
{(segment) => }
Current value: {value ? value.toString() : "(empty)"}
{
const currentTime = now(getLocalTimeZone());
setValue(new Time(currentTime.hour, currentTime.minute, currentTime.second));
}}
>
Set now
setValue(null)}>
Clear
);
}
```
### Disabled State
```tsx
"use client";
import {Description, Label, TimeField} from "@darkcode-ui/react";
import {Time, getLocalTimeZone, now} from "@internationalized/date";
export function Disabled() {
const currentTime = now(getLocalTimeZone());
const timeValue = new Time(currentTime.hour, currentTime.minute, currentTime.second);
return (
Time
{(segment) => }
This time field is disabled
Time
{(segment) => }
This time field is disabled
);
}
```
### With Icons
Add prefix or suffix icons to enhance the time field.
```tsx
"use client";
import {Label, TimeField} from "@darkcode-ui/react";
import {Clock} from "@gravity-ui/icons";
export function WithPrefixIcon() {
return (
Time
{(segment) => }
);
}
```
```tsx
"use client";
import {Label, TimeField} from "@darkcode-ui/react";
import {Clock} from "@gravity-ui/icons";
export function WithSuffixIcon() {
return (
Time
{(segment) => }
);
}
```
```tsx
"use client";
import {Description, Label, TimeField} from "@darkcode-ui/react";
import {ChevronDown, Clock} from "@gravity-ui/icons";
export function WithPrefixAndSuffix() {
return (
Time
{(segment) => }
Enter a time
);
}
```
### Full Width
```tsx
"use client";
import {Label, TimeField} from "@darkcode-ui/react";
import {ChevronDown, Clock} from "@gravity-ui/icons";
export function FullWidth() {
return (
Time
{(segment) => }
Time
{(segment) => }
);
}
```
### On Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` on TimeField.Group to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {Description, Label, Surface, TimeField} from "@darkcode-ui/react";
import {Clock} from "@gravity-ui/icons";
export function OnSurface() {
return (
Time
{(segment) => }
Enter a time
Appointment time
{(segment) => }
Enter a time for your appointment
);
}
```
### Form Example
Complete form example with validation and submission handling.
```tsx
"use client";
import type {Time} from "@internationalized/date";
import {Button, Description, FieldError, Form, Label, TimeField} from "@darkcode-ui/react";
import {Clock} from "@gravity-ui/icons";
import {parseTime} from "@internationalized/date";
import {useState} from "react";
export function FormExample() {
const [value, setValue] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const minTime = parseTime("09:00");
const maxTime = parseTime("17:00");
const isInvalid = value !== null && (value.compare(minTime) < 0 || value.compare(maxTime) > 0);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("Time submitted:", {time: value});
setValue(null);
setIsSubmitting(false);
}, 1500);
};
return (
);
}
```
## Related Components
* **Label**: Accessible label for form controls
* **FieldError**: Inline validation messages for form fields
* **Description**: Helper text for form fields
### Custom Render Function
```tsx
"use client";
import {Label, TimeField} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}
>
Time
{(segment) => }
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {TimeField, Label, Description} from '@darkcode-ui/react';
function CustomTimeField() {
return (
Appointment time
{(segment) => }
Select a time for your appointment.
);
}
```
### Customizing the component classes
TimeField has minimal default styling. Override the `.time-field` class to customize the container styling.
```css
@layer components {
.time-field {
@apply flex flex-col gap-1;
&[data-invalid="true"],
&[aria-invalid="true"] {
[data-slot="description"] {
@apply hidden;
}
}
[data-slot="label"] {
@apply w-fit;
}
[data-slot="description"] {
@apply px-1;
}
}
}
```
### CSS Classes
* `.time-field` – Root container with minimal styling (`flex flex-col gap-1`)
> **Note:** Child components ([Label](/docs/components/label), [Description](/docs/components/description), [FieldError](/docs/components/field-error)) have their own CSS classes and styling. See their respective documentation for customization options. TimeField.Group styling is documented below in the API Reference section.
### Interactive States
TimeField automatically manages these data attributes based on its state:
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` - Automatically hides the description slot when invalid
* **Required**: `[data-required="true"]` - Applied when `isRequired` is true
* **Disabled**: `[data-disabled="true"]` - Applied when `isDisabled` is true
* **Focus Within**: `[data-focus-within="true"]` - Applied when any child input is focused
## API Reference
### TimeField Props
TimeField inherits all props from React Aria's [TimeField](https://react-aria.adobe.com/TimeField) component.
#### Base Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------ | ------- | ------------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: TimeFieldRenderProps) => React.ReactNode` | - | Child components (Label, TimeField.Group, etc.) or render function. |
| `className` | `string \| (values: TimeFieldRenderProps) => string` | - | CSS classes for styling, supports render props. |
| `style` | `React.CSSProperties \| (values: TimeFieldRenderProps) => React.CSSProperties` | - | Inline styles, supports render props. |
| `fullWidth` | `boolean` | `false` | Whether the time field should take full width of its container |
| `id` | `string` | - | The element's unique identifier. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
#### Value Props
| Prop | Type | Default | Description |
| ------------------ | ------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------- |
| `value` | `TimeValue \| null` | - | Current value (controlled). Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `defaultValue` | `TimeValue \| null` | - | Default value (uncontrolled). Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `onChange` | `(value: TimeValue \| null) => void` | - | Handler called when the value changes. |
| `placeholderValue` | `TimeValue \| null` | - | Placeholder time that influences the format of the placeholder. Defaults to 12:00 AM or 00:00 depending on the hour cycle. |
#### Validation Props
| Prop | Type | Default | Description |
| -------------------- | -------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `isRequired` | `boolean` | `false` | Whether user input is required before form submission. |
| `isInvalid` | `boolean` | - | Whether the value is invalid. |
| `minValue` | `TimeValue \| null` | - | The minimum allowed time that a user may select. Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `maxValue` | `TimeValue \| null` | - | The maximum allowed time that a user may select. Uses [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) types. |
| `validate` | `(value: TimeValue) => ValidationError \| true \| null \| undefined` | - | Custom validation function. |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA attributes. |
#### Format Props
| Prop | Type | Default | Description |
| ------------------------- | -------------------------------- | ---------- | ----------------------------------------------------------------------------------------- |
| `granularity` | `'hour' \| 'minute' \| 'second'` | `'minute'` | Determines the smallest unit displayed in the time picker. |
| `hourCycle` | `12 \| 24` | - | Whether to display time in 12 or 24 hour format. By default, determined by locale. |
| `hideTimeZone` | `boolean` | `false` | Whether to hide the time zone abbreviation. |
| `shouldForceLeadingZeros` | `boolean` | - | Whether to always show leading zeros in the hour field. By default, determined by locale. |
#### State Props
| Prop | Type | Default | Description |
| ------------ | --------- | ------- | -------------------------------------------------- |
| `isDisabled` | `boolean` | - | Whether the input is disabled. |
| `isReadOnly` | `boolean` | - | Whether the input can be selected but not changed. |
#### Form Props
| Prop | Type | Default | Description |
| ----------- | --------- | ------- | -------------------------------------------------------------------------------- |
| `name` | `string` | - | Name of the input element, for HTML form submission. Submits as ISO 8601 string. |
| `autoFocus` | `boolean` | - | Whether the element should receive focus on render. |
#### Accessibility Props
| Prop | Type | Default | Description |
| ------------------ | -------- | ------- | ----------------------------------------------------- |
| `aria-label` | `string` | - | Accessibility label when no visible label is present. |
| `aria-labelledby` | `string` | - | ID of elements that label this field. |
| `aria-describedby` | `string` | - | ID of elements that describe this field. |
| `aria-details` | `string` | - | ID of elements with additional details. |
### Composition Components
TimeField works with these separate components that should be imported and used directly:
* **Label** - Field label component from `@darkcode-ui/react`
* **TimeField.Group** - Time input group component (documented below)
* **TimeField.Input** - Input component with segmented editing from `@darkcode-ui/react`
* **TimeField.Segment** - Individual time segment (hour, minute, second, etc.)
* **TimeField.Prefix** / **TimeField.Suffix** - Prefix and suffix slots for the input group
* **Description** - Helper text component from `@darkcode-ui/react`
* **FieldError** - Validation error message from `@darkcode-ui/react`
Each of these components has its own props API. Use them directly within TimeField for composition:
```tsx
import {parseTime} from '@internationalized/date';
import {TimeField, Label, Description, FieldError} from '@darkcode-ui/react';
Appointment Time
{(segment) => }
Select a time between 9:00 AM and 5:00 PM.
Please select a valid time.
```
### TimeValue Types
TimeField uses types from [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/):
* `Time` - Time only (hour, minute, second)
* `CalendarDateTime` - Date with time but no timezone (TimeField displays only the time portion)
* `ZonedDateTime` - Date with time and timezone (TimeField displays only the time portion)
Example:
```tsx
import {parseTime, Time, getLocalTimeZone, now} from '@internationalized/date';
// Parse from string
const time = parseTime('14:30');
// Create from current time
const currentTime = now(getLocalTimeZone());
const timeValue = new Time(currentTime.hour, currentTime.minute, currentTime.second);
// Use in TimeField
{/* ... */}
```
> **Note:** TimeField uses the [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) package for time manipulation, parsing, and type definitions. See the [Internationalized Date documentation](https://react-aria.adobe.com/internationalized/date/) for more information about available types and functions.
### TimeFieldRenderProps
When using render props with `className`, `style`, or `children`, these values are available:
| Prop | Type | Description |
| ---------------- | --------- | ----------------------------------------------- |
| `isDisabled` | `boolean` | Whether the field is disabled. |
| `isInvalid` | `boolean` | Whether the field is currently invalid. |
| `isReadOnly` | `boolean` | Whether the field is read-only. |
| `isRequired` | `boolean` | Whether the field is required. |
| `isFocused` | `boolean` | Whether the field is currently focused. |
| `isFocusWithin` | `boolean` | Whether any child element is focused. |
| `isFocusVisible` | `boolean` | Whether focus is visible (keyboard navigation). |
### TimeField.Group Props
TimeField.Group accepts all props from React Aria's `Group` component plus the following:
| Prop | Type | Default | Description |
| ----------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
### TimeField.Input Props
TimeField.Input accepts all props from React Aria's `DateInput` component plus the following:
| Prop | Type | Default | Description |
| ----------- | -------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the input. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
The `TimeField.Input` component accepts a render prop function that receives date segments. Each segment represents a part of the time (hour, minute, second, etc.).
### TimeField.Segment Props
TimeField.Segment accepts all props from React Aria's `DateSegment` component:
| Prop | Type | Default | Description |
| ----------- | ------------- | ------- | ------------------------------------------------------------- |
| `segment` | `DateSegment` | - | The date segment object from the TimeField.Input render prop. |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
### TimeField.Prefix Props
TimeField.Prefix accepts standard HTML `div` attributes:
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------------------------------------- |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `children` | `ReactNode` | - | Content to display in the prefix slot. |
### TimeField.Suffix Props
TimeField.Suffix accepts standard HTML `div` attributes:
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------------------------------------- |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `children` | `ReactNode` | - | Content to display in the suffix slot. |
## TimeField.Group Styling
### Customizing the component classes
The base classes power every instance. Override them once with `@layer components`.
```css
@layer components {
.date-input-group {
@apply inline-flex h-9 items-center overflow-hidden rounded-field border bg-field text-sm text-field-foreground shadow-field outline-none;
&:hover,
&[data-hovered="true"] {
@apply bg-field-hover;
}
&[data-focus-within="true"],
&:focus-within {
@apply status-focused-field;
}
&[data-invalid="true"] {
@apply status-invalid-field;
}
&[data-disabled="true"],
&[aria-disabled="true"] {
@apply status-disabled;
}
}
.date-input-group__input {
@apply flex flex-1 items-center gap-px rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
}
.date-input-group__segment {
@apply inline-block rounded-md px-0.5 text-end tabular-nums outline-none;
&:focus,
&[data-focused="true"] {
@apply bg-accent-soft text-accent-soft-foreground;
}
}
.date-input-group__prefix,
.date-input-group__suffix {
@apply pointer-events-none shrink-0 text-field-placeholder flex items-center;
}
}
```
### TimeField.Group CSS Classes
* `.date-input-group` – Root container styling
* `.date-input-group__input` – Input wrapper styling
* `.date-input-group__segment` – Individual time segment styling
* `.date-input-group__prefix` – Prefix element styling
* `.date-input-group__suffix` – Suffix element styling
### TimeField.Group Interactive States
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Focus Within**: `[data-focus-within="true"]` or `:focus-within`
* **Invalid**: `[data-invalid="true"]` (also syncs with `aria-invalid`)
* **Disabled**: `[data-disabled="true"]` or `[aria-disabled="true"]`
* **Segment Focus**: `:focus` or `[data-focused="true"]` on segment elements
* **Segment Placeholder**: `[data-placeholder="true"]` on segment elements
# Alert
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/alert
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/alert.mdx
> Display important messages and notifications to users with status indicators
## Import
```tsx
import { Alert } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Alert, Button, CloseButton, Spinner} from "@darkcode-ui/react";
import React from "react";
export function Basic() {
return (
{/* Default - General information */}
New features available
Check out our latest updates including dark mode support and improved accessibility
features.
{/* Accent - Important information with action */}
Update available
A new version of the application is available. Please refresh to get the latest features
and bug fixes.
Refresh
Refresh
{/* Danger - Error with detailed steps */}
Unable to connect to server
We're experiencing connection issues. Please try the following:
Check your internet connection
Refresh the page
Clear your browser cache
Retry
Retry
{/* Without description */}
Profile updated successfully
{/* Custom indicator - Loading state */}
Processing your request
Please wait while we sync your data. This may take a few moments.
{/* Without close button */}
Scheduled maintenance
Our services will be unavailable on Sunday, March 15th from 2:00 AM to 6:00 AM UTC for
scheduled maintenance.
);
}
```
### Anatomy
Import the Alert component and access all parts using dot notation.
```tsx
import { Alert } from '@darkcode-ui/react';
export default () => (
)
```
## Related Components
* **CloseButton**: Button for dismissing overlays
* **Button**: Allows a user to perform an action
* **Spinner**: Loading indicator
## Styling
### Passing Tailwind CSS classes
```tsx
import { Alert } from "@darkcode-ui/react";
function CustomAlert() {
return (
Custom Alert
This alert has custom styling applied
);
}
```
### Customizing the component classes
To customize the Alert component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.alert {
@apply rounded-2xl shadow-lg;
}
.alert__title {
@apply font-bold text-lg;
}
.alert--danger {
@apply border-l-4 border-red-600;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Alert component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/alert.css)):
#### Base Classes
* `.alert` - Base alert container
* `.alert__indicator` - Icon/indicator container
* `.alert__content` - Content wrapper for title and description
* `.alert__title` - Alert title text
* `.alert__description` - Alert description text
#### Status Variant Classes
* `.alert--default` - Default gray status
* `.alert--accent` - Accent blue status
* `.alert--success` - Success green status
* `.alert--warning` - Warning yellow/orange status
* `.alert--danger` - Danger red status
### Interactive States
The Alert component is primarily informational and doesn't have interactive states on the base component. However, it can contain interactive elements like buttons or close buttons.
## API Reference
### Alert Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | ----------- | ------------------------------ |
| `status` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | The visual status of the alert |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert content |
### Alert.Indicator Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ----------------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator icon (defaults to status icon) |
### Alert.Content Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ----------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Content (typically Title and Description) |
### Alert.Title Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert title text |
### Alert.Description Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert description text |
# Empty State
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/empty-state
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/empty-state.mdx
> A placeholder for empty views with icon, title, description, and call-to-action to guide users.
## Import
```tsx
import { EmptyState } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Button, EmptyState} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function Default() {
return (
No results found
We couldn't find anything matching your search. Try adjusting your filters or create
a new item to get started.
Create item
Learn more
);
}
```
### Anatomy
Import the EmptyState component and access all parts using dot notation.
```tsx
import { EmptyState } from '@darkcode-ui/react';
export default () => (
)
```
### Minimal
```tsx
import {Button, EmptyState} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function Minimal() {
return (
Your inbox is empty
New messages will appear here.
Refresh
);
}
```
### Outline
```tsx
import {Button, EmptyState} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function Outline() {
return (
No files uploaded
Drag and drop files here, or click the button below to upload your first file.
Upload file
);
}
```
### Sizes
The `size` prop controls padding and spacing. Available sizes are `sm`, `md` (default), and `lg`.
### Full Height
```tsx
import {Button, EmptyState} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function FullHeight() {
return (
Nothing here yet
This space fills the full height of its container. Start by creating your first project.
New project
);
}
```
### With Avatar
```tsx
import {Avatar, Button, EmptyState} from "@darkcode-ui/react";
import React from "react";
export function WithAvatar() {
return (
JD
No assigned tasks
Jane doesn't have any tasks assigned yet. Assign a task to get started.
Assign task
);
}
```
### With Avatar Group
```tsx
import {Avatar, Button, EmptyState} from "@darkcode-ui/react";
import React from "react";
const users = [
{
id: 1,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-3.jpg",
name: "John",
},
{
id: 2,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-5.jpg",
name: "Kate",
},
{
id: 3,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-20.jpg",
name: "Emily",
},
{
id: 4,
image_url: "https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-23.jpg",
name: "Michael",
},
];
export function WithAvatarGroup() {
return (
{users.map((user) => (
{user.name.charAt(0)}
))}
Invite your team
You haven't added any collaborators yet. Invite teammates to start working together.
Invite people
Copy link
);
}
```
### With Background
```tsx
import {Button, EmptyState} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function WithBackground() {
return (
Ready for launch
Your workspace is set up. Create your first project to get things moving.
Get started
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import { EmptyState, Button } from "@darkcode-ui/react";
function CustomEmptyState() {
return (
No data yet
Get started by creating your first record.
Create
);
}
```
### Customizing the component classes
To customize the EmptyState component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.empty-state {
@apply gap-8;
}
.empty-state__title {
@apply text-lg;
}
.empty-state__media[data-variant="icon"] {
@apply bg-accent-soft text-accent-soft-foreground;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The EmptyState component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/empty-state.css)):
#### Base & Size Classes
* `.empty-state` - Base centered column layout
* `.empty-state--sm` - Small size (less padding and gap)
* `.empty-state--md` - Medium size (default)
* `.empty-state--lg` - Large size (more padding and gap)
#### Element Classes
* `.empty-state__header` - Groups media, title, and description
* `.empty-state__media` - Icon or avatar container
* `.empty-state__title` - Primary heading text
* `.empty-state__description` - Secondary descriptive text
* `.empty-state__content` - Action area for buttons, inputs, or links
#### Data Attributes
* `[data-variant="icon"]` on `.empty-state__media` - Adds circular muted background for icon display
* `[data-variant="default"]` on `.empty-state__media` - Default media styling (no background)
## API Reference
### EmptyState
The root component. Renders a centered container for empty state content.
| Prop | Type | Default | Description |
| ----------- | ---------------------- | ------- | -------------------------------------------- |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size variant controlling padding and spacing |
| `className` | `string` | - | Additional CSS class |
| `children` | `ReactNode` | - | Empty state content |
Also supports all native `div` HTML attributes.
### EmptyState.Header
Groups the media, title, and description elements.
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------- |
| `className` | `string` | - | Additional CSS class |
| `children` | `ReactNode` | - | Header content |
Also supports all native `div` HTML attributes.
### EmptyState.Media
Container for icons, avatars, or images.
| Prop | Type | Default | Description |
| ----------- | --------------------- | ----------- | ----------------------------------------- |
| `variant` | `"default" \| "icon"` | `"default"` | `"icon"` adds a circular muted background |
| `className` | `string` | - | Additional CSS class |
| `children` | `ReactNode` | - | Media content |
Also supports all native `div` HTML attributes.
### EmptyState.Title
Primary heading rendered as an `h3`.
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------- |
| `className` | `string` | - | Additional CSS class |
| `children` | `ReactNode` | - | Title text |
Also supports all native `h3` HTML attributes.
### EmptyState.Description
Secondary descriptive text rendered as a `p`.
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------- |
| `className` | `string` | - | Additional CSS class |
| `children` | `ReactNode` | - | Description text |
Also supports all native `p` HTML attributes.
### EmptyState.Content
Action area for buttons, links, or other interactive elements.
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------- |
| `className` | `string` | - | Additional CSS class |
| `children` | `ReactNode` | - | Action content |
Also supports all native `div` HTML attributes.
# Meter
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/meter
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/meter.mdx
> A meter represents a quantity within a known range, or a fractional value.
## Import
```tsx
import { Meter, Label } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Label, Meter} from "@darkcode-ui/react";
export function Basic() {
return (
Storage
);
}
```
### Anatomy
```tsx
import { Meter, Label } from '@darkcode-ui/react';
export default () => (
Storage
);
```
### Sizes
```tsx
import {Label, Meter} from "@darkcode-ui/react";
export function Sizes() {
return (
Small
Medium
Large
);
}
```
### Colors
```tsx
import {Label, Meter} from "@darkcode-ui/react";
export function Colors() {
return (
Default
Accent
Success
Warning
Danger
);
}
```
### Custom Value Scale
Use `minValue`, `maxValue`, and `formatOptions` to customize the value range and display format.
```tsx
import {Label, Meter} from "@darkcode-ui/react";
export function CustomValue() {
return (
Revenue
);
}
```
### Without Label
When no visible label is needed, use `aria-label` for accessibility.
```tsx
import {Meter} from "@darkcode-ui/react";
export function WithoutLabel() {
return (
);
}
```
## Styling
### Passing Tailwind CSS classes
You can customize individual Meter parts:
```tsx
import { Meter, Label } from '@darkcode-ui/react';
function CustomMeter() {
return (
Storage
);
}
```
### Customizing the component classes
To customize the Meter component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.meter {
@apply w-full gap-2;
}
.meter__track {
@apply h-3 rounded-full;
}
.meter__fill {
@apply rounded-full;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Meter component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/meter.css)):
#### Base & Element Classes
* `.meter` - Base container (grid layout)
* `.meter__output` - Value text display
* `.meter__track` - Track background
* `.meter__fill` - Filled portion of the track
#### Size Classes
* `.meter--sm` - Small size variant (thinner track)
* `.meter--md` - Medium size variant (default)
* `.meter--lg` - Large size variant (thicker track)
#### Color Classes
* `.meter--default` - Default color variant
* `.meter--accent` - Accent color variant
* `.meter--success` - Success color variant
* `.meter--warning` - Warning color variant
* `.meter--danger` - Danger color variant
## API Reference
### Meter Props
Inherits from [React Aria Meter](https://react-spectrum.adobe.com/react-aria/Meter.html).
| Prop | Type | Default | Description |
| --------------- | ------------------------------------------------------------- | -------------------- | ----------------------------------- |
| `value` | `number` | `0` | The current value |
| `minValue` | `number` | `0` | The minimum value |
| `maxValue` | `number` | `100` | The maximum value |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the meter track |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | Color of the fill bar |
| `formatOptions` | `Intl.NumberFormatOptions` | `{style: 'percent'}` | Number format for the value display |
| `valueLabel` | `ReactNode` | - | Custom value label content |
| `children` | `ReactNode \| (values: MeterRenderProps) => ReactNode` | - | Content or render prop |
### MeterRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ------------ | -------- | ----------------------------------- |
| `percentage` | `number` | The percentage of the meter (0-100) |
| `valueText` | `string` | The formatted value text |
# Pressable Feedback
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/pressable-feedback
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/pressable-feedback.mdx
> A press interaction layer adding ripple, highlight, hold-to-confirm, and progress feedback effects to any element
## Import
```tsx
import { PressableFeedback } from '@darkcode-ui/react';
```
## Anatomy
Import the PressableFeedback component and access all parts using dot notation. The root
renders a `button` by default and exposes hover / press / focus state through data attributes,
so the feedback layers can compose freely on top of any content.
```tsx
```
### Usage
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
export function PressableFeedbackDefault() {
return (
Press me
);
}
```
### Comparison
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
const surface =
"relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";
export function PressableFeedbackComparison() {
return (
);
}
```
### Disabled
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
const surface =
"relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";
export function PressableFeedbackDisabled() {
return (
);
}
```
### With Highlight
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
export function PressableFeedbackWithHighlight() {
return (
Highlight
);
}
```
### Standalone Highlight
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
export function PressableFeedbackStandaloneHighlight() {
return (
★
);
}
```
### With Ripple
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
export function PressableFeedbackWithRipple() {
return (
Ripple
);
}
```
### Standalone Ripple
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
export function PressableFeedbackStandaloneRipple() {
return (
+
);
}
```
### With Hold Confirm
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function PressableFeedbackWithHoldConfirm() {
return (
Hold to confirm
Confirmed
);
}
```
### Hold Confirm Callback
```tsx
"use client";
import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function PressableFeedbackHoldConfirmCallback() {
const [count, setCount] = useState(0);
return (
Hold to delete
setCount((c) => c + 1)}>
Deleted
Confirmed {count} times
);
}
```
### Hold Confirm Durations
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
const surface =
"relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";
const durations = [
{label: "Fast · 800ms", value: 800},
{label: "Default · 2s", value: 2000},
{label: "Slow · 3.5s", value: 3500},
];
export function PressableFeedbackHoldConfirmDurations() {
return (
{durations.map(({label, value}) => (
{label}
))}
);
}
```
### Hold Confirm Sweep
```tsx
import type {ComponentProps} from "react";
import {PressableFeedback} from "@darkcode-ui/react";
type Sweep = NonNullable["sweep"]>;
const surface =
"relative flex min-w-32 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground capitalize";
const sweeps: Sweep[] = ["right", "left", "up", "down"];
export function PressableFeedbackHoldConfirmSweep() {
return (
{sweeps.map((sweep) => (
{sweep}
))}
);
}
```
### With Progress Feedback
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function PressableFeedbackWithProgressFeedback() {
return (
Click to progress
Done
);
}
```
### Progress Feedback Callback
```tsx
"use client";
import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function PressableFeedbackProgressFeedbackCallback() {
const [status, setStatus] = useState<"idle" | "done">("idle");
return (
Click to save
setStatus("done")}
onReset={() => setStatus("idle")}
>
Saved
Status: {status}
);
}
```
### Progress Feedback Durations
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
const surface =
"relative flex min-w-36 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground";
const durations = [
{label: "Fast · 800ms", value: 800},
{label: "Default · 2s", value: 2000},
{label: "Slow · 3.5s", value: 3500},
];
export function PressableFeedbackProgressFeedbackDurations() {
return (
{durations.map(({label, value}) => (
{label}
))}
);
}
```
### Progress Feedback No Reset
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function PressableFeedbackProgressFeedbackNoReset() {
return (
Click once
Completed
);
}
```
### Progress Feedback Sweep
```tsx
import type {ComponentProps} from "react";
import {PressableFeedback} from "@darkcode-ui/react";
type Sweep = NonNullable["sweep"]>;
const surface =
"relative flex min-w-32 items-center justify-center gap-2 rounded-2xl border border-border bg-surface px-6 py-4 text-sm font-medium text-foreground capitalize";
const sweeps: Sweep[] = ["right", "left", "up", "down"];
export function PressableFeedbackProgressFeedbackSweep() {
return (
{sweeps.map((sweep) => (
{sweep}
))}
);
}
```
### Pressable Cards
```tsx
import {PressableFeedback} from "@darkcode-ui/react";
const plans = [
{desc: "For individuals getting started", title: "Starter"},
{desc: "For growing teams that need more", title: "Pro"},
{desc: "For organizations at scale", title: "Enterprise"},
];
export function PressableFeedbackPressableCards() {
return (
{plans.map((plan) => (
{plan.title}
{plan.desc}
))}
);
}
```
## Styling
### Passing Tailwind CSS classes
The root and every feedback layer accept a `className`. Keep your own content above the
overlays with `relative z-[1]`:
```tsx
import {PressableFeedback} from '@darkcode-ui/react';
function CustomPressable() {
return (
Press me
);
}
```
### Customizing the component classes
To customize the PressableFeedback classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.pressable-feedback {
--pressable-feedback-highlight-color: var(--accent);
--pressable-feedback-highlight-opacity: 0.12;
}
.pressable-feedback__hold-confirm {
background-color: var(--danger);
color: var(--danger-foreground);
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The PressableFeedback component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/pressable-feedback.css)):
#### Base Classes
* `.pressable-feedback` - Base pressable container with relative positioning and overflow hidden
#### Element Classes
* `.pressable-feedback__highlight` - Opacity-based press overlay
* `.pressable-feedback__ripple` - M3-style radial ripple container
* `.pressable-feedback__ripple-surface` - Ripple animation surface with `::before` (hover) and `::after` (press) pseudo-elements
* `.pressable-feedback__hold-confirm` - Clip-path hold-to-reveal overlay
* `.pressable-feedback__progress-feedback` - Clip-path click-to-progress overlay
#### Interactive States
* **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]` on `.pressable-feedback` (focus ring)
* **Disabled**: `:disabled` or `[aria-disabled="true"]` on `.pressable-feedback` (reduced opacity, no pointer events)
* **Hover**: `:hover` or `[data-hovered="true"]` on the root activates `.pressable-feedback__highlight` opacity
* **Pressed**: `:active` or `[data-pressed="true"]` on the root activates the pressed opacity
#### Data Attributes
* `[data-sweep="right|left|down|up"]` on hold-confirm and progress-feedback - Clip-path sweep direction
* `[data-holding="true"]` on `.pressable-feedback__hold-confirm` - Currently being held
* `[data-complete="true"]` on hold-confirm/progress-feedback - Action completed
* `[data-progressing="true"]` on `.pressable-feedback__progress-feedback` - Progress is active
#### CSS Variables
* `--pressable-feedback-highlight-color` - Highlight overlay color (default: `currentColor`)
* `--pressable-feedback-highlight-opacity` - Hover opacity (default: `0.08`)
* `--pressable-feedback-highlight-pressed-opacity` - Pressed opacity (default: `0.12`)
* `--pressable-feedback-ripple-color` - Ripple color (default: `currentColor`)
* `--pressable-feedback-ripple-hover-opacity` - Ripple hover opacity (default: `0.08`)
* `--pressable-feedback-ripple-pressed-opacity` - Ripple pressed opacity (default: `0.12`)
* `--pressable-feedback-hold-confirm-duration` - Hold duration (default: `2000ms`)
* `--pressable-feedback-hold-confirm-release-duration` - Release snap-back duration (default: `200ms`)
* `--pressable-feedback-progress-feedback-duration` - Progress duration (default: `2000ms`)
* `--pressable-feedback-progress-feedback-release-duration` - Reset snap-back duration (default: `300ms`)
## API Reference
### PressableFeedback
The root pressable container. Renders a `button` element by default.
| Prop | Type | Default | Description |
| ------------ | ------------------- | ------- | --------------------------------------------------------------- |
| `isDisabled` | `boolean` | `false` | Whether the pressable is disabled |
| `children` | `ReactNode` | - | Feedback layers and content |
| `className` | `string` | - | Additional CSS class |
| `render` | `DOMRenderFunction` | - | Custom render function to override the default `button` element |
Also supports all native `button` HTML attributes.
### PressableFeedback.Highlight
Opacity-based hover/press overlay. No additional props beyond standard `span` attributes.
### PressableFeedback.Ripple
M3-style radial ripple effect.
| Prop | Type | Default | Description |
| ---------------------- | --------------- | ------- | -------------------------------------------- |
| `duration` | `number` | `150` | Duration in ms for the ripple grow animation |
| `hoverOpacity` | `number` | `0.08` | Opacity of the hover state |
| `pressedOpacity` | `number` | `0.12` | Opacity of the pressed state |
| `minimumPressDuration` | `number` | `225` | Minimum press duration in ms |
| `isDisabled` | `boolean` | - | Whether the ripple is disabled |
| `className` | `string` | - | Additional CSS class |
| `style` | `CSSProperties` | - | Additional inline styles |
### PressableFeedback.HoldConfirm
Clip-path hold-to-reveal overlay.
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------- | --------- | ----------------------------------------------------- |
| `duration` | `number` | `2000` | Hold duration in ms before the action is confirmed |
| `releaseDuration` | `number` | `200` | Duration in ms for the snap-back animation on release |
| `sweep` | `'right' \| 'left' \| 'down' \| 'up'` | `'right'` | Which edge the clip-path reveal sweeps toward |
| `resetOnComplete` | `boolean` | `true` | Whether to reset the overlay after the hold completes |
| `isDisabled` | `boolean` | - | Whether the hold confirm is disabled |
| `onComplete` | `() => void` | - | Fired when the hold reaches the full duration |
| `children` | `ReactNode` | - | Overlay content shown during the reveal |
| `className` | `string` | - | Additional CSS class |
### PressableFeedback.ProgressFeedback
Clip-path click-to-progress overlay (auto, no hold required).
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------- | --------- | ------------------------------------------------------ |
| `duration` | `number` | `2000` | Progress duration in ms before the action is confirmed |
| `releaseDuration` | `number` | `300` | Duration in ms for the snap-back animation on reset |
| `sweep` | `'right' \| 'left' \| 'down' \| 'up'` | `'right'` | Which edge the clip-path reveal sweeps toward |
| `autoReset` | `boolean` | `true` | Whether to automatically reset after completing |
| `resetDelay` | `number` | `1500` | Delay in ms before resetting after completion |
| `isDisabled` | `boolean` | - | Whether the progress feedback is disabled |
| `onComplete` | `() => void` | - | Fired when the progress reaches the full duration |
| `onReset` | `() => void` | - | Fired when the overlay resets back to idle |
| `children` | `ReactNode` | - | Overlay content shown during the reveal |
| `className` | `string` | - | Additional CSS class |
# ProgressBar
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/progress-bar
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/progress-bar.mdx
> A progress bar shows either determinate or indeterminate progress of an operation over time.
## Import
```tsx
import { ProgressBar, Label } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Label, ProgressBar} from "@darkcode-ui/react";
export function Basic() {
return (
Loading
);
}
```
### Anatomy
```tsx
import { ProgressBar, Label } from '@darkcode-ui/react';
export default () => (
Loading
);
```
### Sizes
```tsx
import {Label, ProgressBar} from "@darkcode-ui/react";
export function Sizes() {
return (
);
}
```
### Colors
```tsx
import {Label, ProgressBar} from "@darkcode-ui/react";
export function Colors() {
return (
Default
Accent
Success
Warning
Danger
);
}
```
### Indeterminate
Use `isIndeterminate` when progress cannot be determined.
```tsx
import {Label, ProgressBar} from "@darkcode-ui/react";
export function Indeterminate() {
return (
Loading...
);
}
```
### Custom Value Scale
Use `minValue`, `maxValue`, and `formatOptions` to customize the value range and display format.
```tsx
"use client";
import {Label, ListBox, NumberField, ProgressBar, Select, Separator} from "@darkcode-ui/react";
import {useState} from "react";
const formatStyleOptions: {label: string; value: string}[] = [
{label: "Currency", value: "currency"},
{label: "Percent", value: "percent"},
{label: "Decimal", value: "decimal"},
{label: "Unit", value: "unit"},
];
const formatOptionsMap: Record = {
currency: {currency: "USD", style: "currency"},
decimal: {style: "decimal"},
percent: {style: "percent"},
unit: {style: "unit", unit: "mile"},
};
export function CustomValue() {
const [value, setValue] = useState(750);
const [minValue, setMinValue] = useState(0);
const [maxValue, setMaxValue] = useState(1000);
const [format, setFormat] = useState("percent");
return (
Options
setValue(v)}
>
Value
{
setMinValue(v);
if (value < v) setValue(v);
}}
>
Min Value
{
setMaxValue(v);
if (value > v) setValue(v);
}}
>
Max Value
setFormat(key as string)}>
Format
{formatStyleOptions.map((option) => (
{option.label}
))}
);
}
```
### Without Label
When no visible label is needed, use `aria-label` for accessibility.
```tsx
import {ProgressBar} from "@darkcode-ui/react";
export function WithoutLabel() {
return (
);
}
```
## Styling
### Passing Tailwind CSS classes
You can customize individual ProgressBar parts:
```tsx
import { ProgressBar, Label } from '@darkcode-ui/react';
function CustomProgressBar() {
return (
Loading
);
}
```
### Customizing the component classes
To customize the ProgressBar component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.progress-bar {
@apply w-full gap-2;
}
.progress-bar__track {
@apply h-3 rounded-full;
}
.progress-bar__fill {
@apply rounded-full;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The ProgressBar component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/progress-bar.css)):
#### Base & Element Classes
* `.progress-bar` - Base container (grid layout)
* `.progress-bar__output` - Value text display
* `.progress-bar__track` - Track background
* `.progress-bar__fill` - Filled portion of the track
#### Size Classes
* `.progress-bar--sm` - Small size variant (thinner track)
* `.progress-bar--md` - Medium size variant (default)
* `.progress-bar--lg` - Large size variant (thicker track)
#### Color Classes
* `.progress-bar--default` - Default color variant
* `.progress-bar--accent` - Accent color variant
* `.progress-bar--success` - Success color variant
* `.progress-bar--warning` - Warning color variant
* `.progress-bar--danger` - Danger color variant
## API Reference
### ProgressBar Props
Inherits from [React Aria ProgressBar](https://react-spectrum.adobe.com/react-aria/ProgressBar.html).
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------------------------------- | -------------------- | ----------------------------------- |
| `value` | `number` | `0` | The current value |
| `minValue` | `number` | `0` | The minimum value |
| `maxValue` | `number` | `100` | The maximum value |
| `isIndeterminate` | `boolean` | `false` | Whether progress is indeterminate |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the progress track |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | Color of the fill bar |
| `formatOptions` | `Intl.NumberFormatOptions` | `{style: 'percent'}` | Number format for the value display |
| `valueLabel` | `ReactNode` | - | Custom value label content |
| `children` | `ReactNode \| (values: ProgressBarRenderProps) => ReactNode` | - | Content or render prop |
### ProgressBarRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ----------------- | --------- | -------------------------------------- |
| `percentage` | `number` | The percentage of the progress (0-100) |
| `valueText` | `string` | The formatted value text |
| `isIndeterminate` | `boolean` | Whether progress is indeterminate |
# ProgressCircle
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/progress-circle
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/progress-circle.mdx
> A circular progress indicator that shows determinate or indeterminate progress.
## Import
```tsx
import { ProgressCircle } from '@darkcode-ui/react';
```
### Usage
```tsx
import {ProgressCircle} from "@darkcode-ui/react";
export function Basic() {
return (
);
}
```
### Anatomy
```tsx
import { ProgressCircle } from '@darkcode-ui/react';
export default () => (
);
```
### Sizes
```tsx
import {ProgressCircle} from "@darkcode-ui/react";
export function Sizes() {
return (
);
}
```
### Colors
```tsx
import {ProgressCircle} from "@darkcode-ui/react";
export function Colors() {
return (
);
}
```
### Indeterminate
Use `isIndeterminate` when progress cannot be determined.
```tsx
import {ProgressCircle} from "@darkcode-ui/react";
export function Indeterminate() {
return (
);
}
```
### With Label
```tsx
import {Label, ProgressCircle} from "@darkcode-ui/react";
export function WithLabel() {
return (
);
}
```
### Custom SVG Props
Since each part is a composable component, you can override SVG attributes like `strokeWidth`, `r`, `cx`, `cy`, and `viewBox` directly.
```tsx
import {ProgressCircle} from "@darkcode-ui/react";
export function CustomSvg() {
return (
);
}
```
## Styling
### Passing Tailwind CSS classes
You can customize individual ProgressCircle parts:
```tsx
import { ProgressCircle } from '@darkcode-ui/react';
function CustomProgressCircle() {
return (
);
}
```
### Customizing the component classes
To customize the ProgressCircle component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.progress-circle {
@apply inline-flex;
}
.progress-circle__track {
@apply size-12;
}
.progress-circle__fill-circle {
stroke: purple;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The ProgressCircle component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/progress-circle.css)):
#### Base & Element Classes
* `.progress-circle` - Base container
* `.progress-circle__track` - SVG element
* `.progress-circle__track-circle` - Background circle
* `.progress-circle__fill-circle` - Progress arc
#### Size Classes
* `.progress-circle--sm` - Small size variant
* `.progress-circle--md` - Medium size variant (default)
* `.progress-circle--lg` - Large size variant
#### Color Classes
* `.progress-circle--default` - Default color variant
* `.progress-circle--accent` - Accent color variant
* `.progress-circle--success` - Success color variant
* `.progress-circle--warning` - Warning color variant
* `.progress-circle--danger` - Danger color variant
## API Reference
### ProgressCircle Props
Inherits from [React Aria ProgressBar](https://react-spectrum.adobe.com/react-aria/ProgressBar.html).
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------------------------------- | -------------------- | ----------------------------------- |
| `value` | `number` | `0` | The current value |
| `minValue` | `number` | `0` | The minimum value |
| `maxValue` | `number` | `100` | The maximum value |
| `isIndeterminate` | `boolean` | `false` | Whether progress is indeterminate |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the circle |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | Color of the progress arc |
| `formatOptions` | `Intl.NumberFormatOptions` | `{style: 'percent'}` | Number format for the value display |
| `children` | `ReactNode \| (values: ProgressBarRenderProps) => ReactNode` | - | Content or render prop |
### ProgressBarRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ----------------- | --------- | -------------------------------------- |
| `percentage` | `number` | The percentage of the progress (0-100) |
| `valueText` | `string` | The formatted value text |
| `isIndeterminate` | `boolean` | Whether progress is indeterminate |
# Skeleton
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/skeleton
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/skeleton.mdx
> Skeleton is a placeholder to show a loading state and the expected shape of a component.
## Import
```tsx
import { Skeleton } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Skeleton} from "@darkcode-ui/react";
export function Basic() {
return (
);
}
```
### Text Content
```tsx
import {Skeleton} from "@darkcode-ui/react";
export function TextContent() {
return (
);
}
```
### User Profile
```tsx
import {Skeleton} from "@darkcode-ui/react";
export function UserProfile() {
return (
);
}
```
### List Items
```tsx
import {Skeleton} from "@darkcode-ui/react";
export function List() {
return (
{Array.from({length: 3}).map((_, index) => (
))}
);
}
```
### Animation Types
```tsx
import {Skeleton} from "@darkcode-ui/react";
export function AnimationTypes() {
return (
);
}
```
### Grid
```tsx
import {Skeleton} from "@darkcode-ui/react";
export function Grid() {
return (
);
}
```
### Single Shimmer
A synchronized shimmer effect that passes over all skeleton elements at once. Apply the `skeleton--shimmer` class to a parent container and set `animationType="none"` on child skeletons.
```tsx
import {Skeleton} from "@darkcode-ui/react";
export function SingleShimmer() {
return (
);
}
```
## Related Components
* **Card**: Content container with header, body, and footer
* **Avatar**: Display user profile images
## Styling
### Global Animation Configuration
You can set a default animation type for all Skeleton components in your application by defining the `--skeleton-animation` CSS variable:
```css
/* In your global CSS file */
:root {
/* Possible values: shimmer, pulse, none */
--skeleton-animation: pulse;
}
/* You can also set different values for light/dark themes */
.light, [data-theme="light"] {
--skeleton-animation: shimmer;
}
.dark, [data-theme="dark"] {
--skeleton-animation: pulse;
}
```
This global setting will be overridden by the `animationType` prop when specified on individual components.
### Passing Tailwind CSS classes
```tsx
import { Skeleton } from '@darkcode-ui/react';
function CustomSkeleton() {
return (
);
}
```
### Customizing the component classes
To customize the Skeleton component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
/* Base skeleton styles */
.skeleton {
@apply bg-surface-secondary/50; /* Change base background */
}
/* Shimmer animation gradient */
.skeleton--shimmer:before {
@apply viasurface; /* Change shimmer gradient color */
}
/* Pulse animation */
.skeleton--pulse {
@apply animate-pulse opacity-75; /* Customize pulse animation */
}
/* No animation variant */
.skeleton--none {
@apply opacity-50; /* Style for static skeleton */
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Skeleton component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/skeleton.css)):
#### Base Class
`.skeleton` - Base skeleton styles with background and rounded corners
#### Animation Variant Classes
* `.skeleton--shimmer` - Adds shimmer animation with gradient effect (default)
* `.skeleton--pulse` - Adds pulse animation using Tailwind's animate-pulse
* `.skeleton--none` - No animation, static skeleton
### Animation
The Skeleton component supports three animation types, each with different visual effects:
#### Shimmer Animation
The shimmer effect creates a gradient that moves across the skeleton element:
```css
.skeleton--shimmer:before {
@apply animate-skeleton via-surface-3 absolute inset-0 -translate-x-full
bg-gradient-to-r from-transparent to-transparent content-[''];
}
```
The shimmer animation is defined in the theme using:
```css
@theme inline {
--animate-skeleton: skeleton 2s linear infinite;
@keyframes skeleton {
100% {
transform: translateX(200%);
}
}
}
```
#### Pulse Animation
The pulse animation uses Tailwind's built-in `animate-pulse` utility:
```css
.skeleton--pulse {
@apply animate-pulse;
}
```
#### No Animation
For static skeletons without any animation:
```css
.skeleton--none {
/* No animation styles applied */
}
```
## API Reference
### Skeleton Props
| Prop | Type | Default | Description |
| --------------- | -------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- |
| `animationType` | `"shimmer" \| "pulse" \| "none"` | `"shimmer"` or CSS variable | The animation type for the skeleton. Can be globally configured via `--skeleton-animation` CSS variable |
| `className` | `string` | - | Additional CSS classes |
# Spinner
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/spinner
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(feedback)/spinner.mdx
> A loading indicator component to show pending states
## Import
```tsx
import { Spinner } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Spinner} from "@darkcode-ui/react";
export function SpinnerBasic() {
return (
);
}
```
### Colors
```tsx
import {Spinner} from "@darkcode-ui/react";
export function SpinnerColors() {
return (
Current
Accent
Success
Warning
Danger
);
}
```
### Sizes
```tsx
import {Spinner} from "@darkcode-ui/react";
export function SpinnerSizes() {
return (
Small
Medium
Large
Extra Large
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {Spinner} from '@darkcode-ui/react';
function CustomSpinner() {
return (
);
}
```
### Customizing the component classes
To customize the Spinner component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.spinner {
@apply animate-spin;
}
.spinner--accent {
color: var(--accent);
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Spinner component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/spinner.css)):
#### Base & Size Classes
* `.spinner` - Base spinner styles with default size
* `.spinner--sm` - Small size variant
* `.spinner--md` - Medium size variant (default)
* `.spinner--lg` - Large size variant
* `.spinner--xl` - Extra large size variant
#### Color Classes
* `.spinner--current` - Inherits current text color
* `.spinner--accent` - Accent color variant
* `.spinner--danger` - Danger color variant
* `.spinner--success` - Success color variant
* `.spinner--warning` - Warning color variant
## API Reference
### Spinner Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | ----------- | ---------------------------- |
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size of the spinner |
| `color` | `"current" \| "accent" \| "success" \| "warning" \| "danger"` | `"current"` | Color variant of the spinner |
| `className` | `string` | - | Additional CSS classes |
# Cell Slider
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/cell-slider
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/cell-slider.mdx
> A range slider styled as a settings cell with label, value display, and step configuration
## Import
```tsx
import { CellSlider } from '@darkcode-ui/react';
```
## Usage
```tsx
"use client";
import {CellSlider} from "@darkcode-ui/react";
export function Default() {
return (
Brightness
);
}
```
## Anatomy
Import the CellSlider component and access all parts using dot notation.
```tsx
import { CellSlider } from '@darkcode-ui/react';
export default () => (
)
```
## Controlled
Pass `value` and `onChange` to control the slider value.
```tsx
"use client";
import {CellSlider} from "@darkcode-ui/react";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(40);
return (
setValue(v as number)}>
Volume
Current value: {value}
);
}
```
## Disabled
```tsx
"use client";
import {CellSlider} from "@darkcode-ui/react";
export function Disabled() {
return (
Brightness
);
}
```
## Integer Step
Use `step`, `minValue`, and `maxValue` to constrain the slider to whole numbers.
```tsx
"use client";
import {CellSlider} from "@darkcode-ui/react";
export function IntegerStep() {
return (
Players
);
}
```
## Secondary Group
Use `variant="secondary"` to render cells with the secondary surface style, ideal for stacked groups.
```tsx
"use client";
import {CellSlider} from "@darkcode-ui/react";
export function SecondaryGroup() {
return (
Brightness
Contrast
);
}
```
## Settings Group
Stack multiple cells inside a surface container to build a settings panel.
```tsx
"use client";
import {CellSlider} from "@darkcode-ui/react";
export function SettingsGroup() {
return (
Brightness
Volume
Warmth
);
}
```
## Variants
```tsx
"use client";
import {CellSlider} from "@darkcode-ui/react";
export function Variants() {
return (
Default
Secondary
);
}
```
## Styling
CellSlider wraps the [Slider](/react/components/slider) component, so all Slider styling
patterns apply. The cell layout is provided by the BEM classes below.
### CSS Classes
The CellSlider component uses the following CSS classes:
#### Base Classes
* `.cell-slider` - Root wrapper
#### Element Classes
* `.cell-slider__track` - The visible cell row (Slider.Track)
* `.cell-slider__track--default` - Default variant track styling
* `.cell-slider__track--secondary` - Secondary variant track styling
* `.cell-slider__fill` - Accent tint showing the current value
* `.cell-slider__thumb` - Transparent hit area with a `::after` pill indicator
* `.cell-slider__label` - Leading text label (absolutely positioned left)
* `.cell-slider__output` - Value display (absolutely positioned right)
## API Reference
### CellSlider
The root component. Wraps DarkCode UI [Slider](/react/components/slider) with cell-style
layout. Always renders in horizontal orientation.
| Prop | Type | Default | Description |
| --------- | -------------------------- | ----------- | --------------------- |
| `variant` | `'default' \| 'secondary'` | `'default'` | Visual style variant. |
Also supports all [DarkCode UI Slider](/react/components/slider) props except `variant` and `orientation`.
### CellSlider.Label
Leading text label, absolutely positioned on the left.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ----------- |
| `children` | `ReactNode` | - | Label text. |
Also supports all native `span` HTML attributes.
### CellSlider.Output
Value display, absolutely positioned on the right.
Also supports all DarkCode UI `Slider.Output` props.
### CellSlider.Track
The visible cell row serving as the slider track. Contains fill, thumb, label, and output as children.
Also supports all DarkCode UI `Slider.Track` props.
### CellSlider.Fill
Subtle accent tint showing the current slider value.
Also supports all DarkCode UI `Slider.Fill` props.
### CellSlider.Thumb
Transparent hit area for drag and keyboard accessibility. The visible indicator is a
`::after` pseudo-element (thin 3px pill).
Also supports all DarkCode UI `Slider.Thumb` props.
## Accessibility
CellSlider is built on the same React Aria Slider primitive as the
[Slider](/react/components/slider) component and provides:
* Full keyboard navigation support (Arrow keys, Home, End, Page Up/Down)
* Screen reader announcements for value changes
* Proper focus management
* Support for disabled states
* Internationalization with locale-aware value formatting
For more information, see the [React Aria Slider documentation](https://react-spectrum.adobe.com/react-aria/Slider.html).
# CheckboxGroup
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/checkbox-group
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/checkbox-group.mdx
> A checkbox group component for managing multiple checkbox selections
## Import
```tsx
import { CheckboxGroup, Checkbox, Label, Description } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@darkcode-ui/react";
export function Basic() {
return (
Select your interests
Choose all that apply
Coding
Love building software
Design
Enjoy creating beautiful interfaces
Writing
Passionate about content creation
);
}
```
### Anatomy
Import the CheckboxGroup component and access all parts using dot notation.
```tsx
import {CheckboxGroup, Checkbox, Label, Description, FieldError} from '@darkcode-ui/react';
export default () => (
{/* Optional */}
Label
{/* Optional, sibling of Checkbox.Content */}
{/* Optional */}
);
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Checkbox, CheckboxGroup, Description, Label, Surface} from "@darkcode-ui/react";
export function OnSurface() {
return (
Select your interests
Choose all that apply
Coding
Love building software
Design
Enjoy creating beautiful interfaces
Writing
Passionate about content creation
);
}
```
### With Custom Indicator
```tsx
"use client";
import {Checkbox, CheckboxGroup, Description, Label} from "@darkcode-ui/react";
export function WithCustomIndicator() {
return (
Features
Select the features you want
{({isSelected}) =>
isSelected ? (
) : null
}
Email notifications
Receive updates via email
{({isSelected}) =>
isSelected ? (
) : null
}
Newsletter
Get weekly newsletters
);
}
```
### Indeterminate
```tsx
"use client";
import {Checkbox, CheckboxGroup, Label} from "@darkcode-ui/react";
import {useState} from "react";
export function Indeterminate() {
const [selected, setSelected] = useState(["coding"]);
const allOptions = ["coding", "design", "writing"];
return (
0 && selected.length < allOptions.length}
isSelected={selected.length === allOptions.length}
name="select-all"
onChange={(isSelected: boolean) => {
setSelected(isSelected ? allOptions : []);
}}
>
Select all
Coding
Design
Writing
);
}
```
### Controlled
```tsx
"use client";
import {Checkbox, CheckboxGroup, Label} from "@darkcode-ui/react";
import {useState} from "react";
export function Controlled() {
const [selected, setSelected] = useState(["coding", "design"]);
return (
Your skills
Coding
Design
Writing
Selected: {selected.join(", ") || "None"}
);
}
```
### Validation
```tsx
"use client";
import {Button, Checkbox, CheckboxGroup, FieldError, Form, Label} from "@darkcode-ui/react";
export function Validation() {
return (
);
}
```
### Disabled
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@darkcode-ui/react";
export function Disabled() {
return (
Features
Feature selection is temporarily disabled
Feature 1
This feature is coming soon
Feature 2
This feature is coming soon
);
}
```
### Features and Add-ons Example
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@darkcode-ui/react";
import {Bell, Comment, Envelope} from "@gravity-ui/icons";
import clsx from "clsx";
export function FeaturesAndAddOns() {
const addOns = [
{
description: "Receive updates via email",
icon: Envelope,
title: "Email Notifications",
value: "email",
},
{
description: "Get instant SMS notifications",
icon: Comment,
title: "SMS Alerts",
value: "sms",
},
{
description: "Browser and mobile push alerts",
icon: Bell,
title: "Push Notifications",
value: "push",
},
];
return (
Notification preferences
Choose how you want to receive updates
{addOns.map((addon) => (
{addon.title}
{addon.description}
))}
);
}
```
### Custom Render Function
```tsx
"use client";
import {Checkbox, CheckboxGroup, Description, Label} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}>
Select your interests
Choose all that apply
Coding
Love building software
Design
Enjoy creating beautiful interfaces
Writing
Passionate about content creation
);
}
```
## Related Components
* **Checkbox**: Binary choice input control
* **Label**: Accessible label for form controls
* **Fieldset**: Group related form controls with legends
## Styling
### Passing Tailwind CSS classes
You can customize the CheckboxGroup component:
```tsx
import { CheckboxGroup, Checkbox, Label } from '@darkcode-ui/react';
function CustomCheckboxGroup() {
return (
Option 1
);
}
```
### Customizing the component classes
To customize the CheckboxGroup component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.checkbox-group {
@apply flex flex-col gap-2;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The CheckboxGroup component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/checkbox-group.css)):
* `.checkbox-group` - Base checkbox group container
## API Reference
### CheckboxGroup Props
Inherits from [React Aria CheckboxGroup](https://react-spectrum.adobe.com/react-aria/CheckboxGroup.html).
| Prop | Type | Default | Description |
| -------------- | -------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------- |
| `value` | `string[]` | - | The current selected values (controlled) |
| `defaultValue` | `string[]` | - | The default selected values (uncontrolled) |
| `onChange` | `(value: string[]) => void` | - | Handler called when the selected values change |
| `isDisabled` | `boolean` | `false` | Whether the checkbox group is disabled |
| `isRequired` | `boolean` | `false` | Whether the checkbox group is required |
| `isReadOnly` | `boolean` | `false` | Whether the checkbox group is read only |
| `isInvalid` | `boolean` | `false` | Whether the checkbox group is in an invalid state |
| `name` | `string` | - | The name of the checkbox group, used when submitting an HTML form |
| `children` | `React.ReactNode \| (values: CheckboxGroupRenderProps) => React.ReactNode` | - | Checkbox group content or render prop |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### CheckboxGroupRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ------------ | ---------- | ------------------------------------------------- |
| `value` | `string[]` | The currently selected values |
| `isDisabled` | `boolean` | Whether the checkbox group is disabled |
| `isReadOnly` | `boolean` | Whether the checkbox group is read only |
| `isInvalid` | `boolean` | Whether the checkbox group is in an invalid state |
| `isRequired` | `boolean` | Whether the checkbox group is required |
# Checkbox
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/checkbox
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/checkbox.mdx
> Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.
## Import
```tsx
import { Checkbox, Label } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Checkbox, Label} from "@darkcode-ui/react";
export function Basic() {
return (
Accept terms and conditions
);
}
```
### Anatomy
Import the Checkbox component and access all parts using dot notation.
```tsx
import { Checkbox, Description } from '@darkcode-ui/react';
export default () => (
Label
{/* Optional, sibling of Checkbox.Content */}
);
```
### Disabled
```tsx
import {Checkbox, Description, Label} from "@darkcode-ui/react";
export function Disabled() {
return (
Premium Feature
This feature is coming soon
);
}
```
### Default Selected
```tsx
import {Checkbox, Label} from "@darkcode-ui/react";
export function DefaultSelected() {
return (
Enable email notifications
);
}
```
### Controlled
```tsx
"use client";
import {Checkbox, Label} from "@darkcode-ui/react";
import {useState} from "react";
export function Controlled() {
const [isSelected, setIsSelected] = useState(true);
return (
Email notifications
Status: {isSelected ? "Enabled" : "Disabled"}
);
}
```
### Indeterminate
```tsx
"use client";
import {Checkbox, Description, Label} from "@darkcode-ui/react";
import {useState} from "react";
export function Indeterminate() {
const [isIndeterminate, setIsIndeterminate] = useState(true);
const [isSelected, setIsSelected] = useState(false);
return (
{
setIsSelected(selected);
setIsIndeterminate(false);
}}
>
Select all
Shows indeterminate state (dash icon)
);
}
```
### With Label
```tsx
import {Checkbox, Label} from "@darkcode-ui/react";
export function WithLabel() {
return (
Send me marketing emails
);
}
```
### With Description
```tsx
import {Checkbox, Description, Label} from "@darkcode-ui/react";
export function WithDescription() {
return (
Email notifications
Get notified when someone mentions you in a comment
);
}
```
### Render Props
```tsx
"use client";
import {Checkbox, Description, Label} from "@darkcode-ui/react";
export function RenderProps() {
return (
{({isSelected}) => (
<>
{isSelected ? "Terms accepted" : "Accept terms"}
{isSelected ? "Thank you for accepting" : "Please read and accept the terms"}
>
)}
);
}
```
### Form Integration
```tsx
"use client";
import {Button, Checkbox, Label} from "@darkcode-ui/react";
import React from "react";
export function Form() {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
alert(
`Form submitted with:\n${Array.from(formData.entries())
.map(([key, value]) => `${key}: ${value}`)
.join("\n")}`,
);
};
return (
);
}
```
### Invalid
```tsx
import {Checkbox, Description, Label} from "@darkcode-ui/react";
export function Invalid() {
return (
I agree to the terms
You must accept the terms to continue
);
}
```
### Custom Indicator
```tsx
"use client";
import {Checkbox, Label} from "@darkcode-ui/react";
export function CustomIndicator() {
return (
{({isSelected}) =>
isSelected ? (
) : null
}
Heart
{({isSelected}) =>
isSelected ? (
) : null
}
Plus
{({isIndeterminate}) =>
isIndeterminate ? (
) : null
}
Indeterminate
);
}
```
### Full Rounded
```tsx
import {Checkbox, Label} from "@darkcode-ui/react";
export function FullRounded() {
return (
Rounded checkboxes
Small size
Default size
Large size
Extra large size
);
}
```
### Variants
The Checkbox component supports two visual variants:
* **`primary`** (default) - Standard styling with default background, suitable for most use cases
* **`secondary`** - Lower emphasis variant, suitable for use in Surface components
```tsx
import {Checkbox, Description, Label} from "@darkcode-ui/react";
export function Variants() {
return (
Primary variant
Primary checkbox
Standard styling with default background
Secondary variant
Secondary checkbox
Lower emphasis variant for use in surfaces
);
}
```
### Custom Render Function
```tsx
"use client";
import {Checkbox} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}>
Accept terms and conditions
);
}
```
## Related Components
* **Label**: Accessible label for form controls
* **CheckboxGroup**: Group of checkboxes with shared state
* **Description**: Helper text for form fields
## Styling
### Passing Tailwind CSS classes
You can customize individual Checkbox components:
```tsx
import { Checkbox } from '@darkcode-ui/react';
function CustomCheckbox() {
return (
Custom Checkbox
);
}
```
### Customizing the component classes
To customize the Checkbox component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.checkbox {
@apply inline-flex gap-3 items-center;
}
.checkbox__control {
@apply size-5 border-2 border-gray-400 rounded data-[selected=true]:bg-blue-500 data-[selected=true]:border-blue-500;
/* Animated background indicator */
&::before {
@apply bg-accent pointer-events-none absolute inset-0 z-0 origin-center scale-50 rounded-md opacity-0 content-[''];
transition:
scale 200ms linear,
opacity 200ms linear,
background-color 200ms ease-out;
}
/* Show indicator when selected */
&[data-selected="true"]::before {
@apply scale-100 opacity-100;
}
}
.checkbox__indicator {
@apply text-white;
}
.checkbox__content {
@apply flex flex-col gap-1;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Checkbox component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/checkbox.css)):
* `.checkbox` - Base checkbox container
* `.checkbox__control` - Checkbox control box
* `.checkbox__indicator` - Checkbox checkmark indicator
* `.checkbox__content` - Optional content container
### Interactive States
The checkbox supports both CSS pseudo-classes and data attributes for flexibility:
* **Selected**: `[data-selected="true"]` or `[aria-checked="true"]` (shows checkmark and background color change)
* **Indeterminate**: `[data-indeterminate="true"]` (shows indeterminate state with dash)
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` (shows error state with danger colors)
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring)
* **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events)
* **Pressed**: `:active` or `[data-pressed="true"]`
## API Reference
### Checkbox Props
Inherits from [React Aria Checkbox](https://react-spectrum.adobe.com/react-aria/Checkbox.html).
| Prop | Type | Default | Description |
| ----------------- | --------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `isSelected` | `boolean` | `false` | Whether the checkbox is checked |
| `defaultSelected` | `boolean` | `false` | Whether the checkbox is checked by default (uncontrolled) |
| `isIndeterminate` | `boolean` | `false` | Whether the checkbox is in an indeterminate state |
| `isDisabled` | `boolean` | `false` | Whether the checkbox is disabled |
| `isInvalid` | `boolean` | `false` | Whether the checkbox is invalid |
| `isReadOnly` | `boolean` | `false` | Whether the checkbox is read only |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `name` | `string` | - | The name of the input element, used when submitting an HTML form |
| `value` | `string` | - | The value of the input element, used when submitting an HTML form |
| `onChange` | `(isSelected: boolean) => void` | - | Handler called when the checkbox value changes |
| `children` | `React.ReactNode \| (values: CheckboxRenderProps) => React.ReactNode` | - | Checkbox content or render prop |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### CheckboxRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ----------------- | --------- | ------------------------------------------------- |
| `isSelected` | `boolean` | Whether the checkbox is currently checked |
| `isIndeterminate` | `boolean` | Whether the checkbox is in an indeterminate state |
| `isHovered` | `boolean` | Whether the checkbox is hovered |
| `isPressed` | `boolean` | Whether the checkbox is currently pressed |
| `isFocused` | `boolean` | Whether the checkbox is focused |
| `isFocusVisible` | `boolean` | Whether the checkbox is keyboard focused |
| `isDisabled` | `boolean` | Whether the checkbox is disabled |
| `isReadOnly` | `boolean` | Whether the checkbox is read only |
# Description
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/description
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/description.mdx
> Provides supplementary text for form fields and other components
## Import
```tsx
import { Description } from '@darkcode-ui/react';
```
## Usage
```tsx
import {Description, Input, Label} from "@darkcode-ui/react";
export function Basic() {
return (
Email
We'll never share your email with anyone else.
);
}
```
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **Input**: Single-line text input built on React Aria
* **TextArea**: Multiline text input with focus management
## API
### Description Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------------------------ |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The content of the description |
## Accessibility
The Description component enhances accessibility by:
* Using semantic HTML that screen readers can identify
* Providing the `slot="description"` attribute for React Aria integration
* Supporting proper text contrast ratios
## Styling
The Description component uses the following CSS classes:
* `.description` - Base description styles with `muted` text color
## Examples
### With Form Fields
```tsx
Password
Must be at least 8 characters with one uppercase letter
```
### Integration with TextField
```tsx
import {TextField, Label, Input, Description} from '@darkcode-ui/react';
Email
We'll never share your email
```
When using the [TextField](./text-field) component, accessibility attributes are automatically applied to the label and description.
# DropZone
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/drop-zone
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/drop-zone.mdx
> A drag-and-drop file upload area with file type filtering, size limits, and a file list preview.
## Import
```tsx
import { DropZone } from '@darkcode-ui/react';
```
DropZone is a composable component. You own the file state and render the file list yourself, which keeps the upload flow fully under your control.
### Usage
```tsx
import {DropZone} from "@darkcode-ui/react";
export function Default() {
return (
Drag files here or click to browse
PDF, DOCX, PNG, or JPG up to 10 MB.
Select files
);
}
```
### Anatomy
Import the DropZone component and access all parts using dot notation.
```tsx
import { DropZone } from '@darkcode-ui/react';
Drag files here or click to browse
PDF, DOCX, PNG, or JPG up to 10 MB.
Select files
Proposal.pdf
2.4 MB
;
```
### With File List
Render `DropZone.FileList` with one `DropZone.FileItem` per file. The list animates items in and out as your state changes.
```tsx
import {DropZone} from "@darkcode-ui/react";
export function WithFileList() {
return (
Drag files here or click to browse
PDF, DOCX, PNG, or JPG up to 10 MB.
Select files
Proposal.pdf
2.4 MB
Cover.png
Complete · 1.1 MB
);
}
```
### Compact File List
Pass `compact` to `DropZone.FileList` for a denser layout, useful when many files are listed.
```tsx
import {DropZone} from "@darkcode-ui/react";
export function CompactFileList() {
return (
Drag files here or click to browse
Select files
Report.docx
820 KB
Assets.zip
14.2 MB
Hero.png
1.1 MB
);
}
```
### Disabled
Set `isDisabled` on `DropZone.Area` to prevent dropping. The Trigger inside the area is disabled automatically.
```tsx
import {DropZone} from "@darkcode-ui/react";
export function Disabled() {
return (
Drag files here or click to browse
Uploads are currently disabled.
Select files
);
}
```
### Image Only
Use the `accept` prop on `DropZone.Input` to restrict the file picker to specific file types.
```tsx
import {DropZone} from "@darkcode-ui/react";
export function ImageOnly() {
return (
Drag images here or click to browse
PNG, JPG, or GIF up to 5 MB.
Select images
);
}
```
### Max Size Limit
Validate file size in your `onSelect` handler and mark oversized files with `status="failed"`, then offer a retry.
```tsx
"use client";
import {DropZone} from "@darkcode-ui/react";
import React from "react";
const MAX_SIZE = 5 * 1024 * 1024; // 5 MB
interface UploadFile {
id: string;
name: string;
size: number;
status: "complete" | "failed";
}
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";
const units = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / 1024 ** i).toFixed(1)} ${units[i]}`;
}
export function MaxSizeLimit() {
const [files, setFiles] = React.useState([
{id: "seed", name: "Archive.zip", size: 14_680_064, status: "failed"},
]);
const addFiles = (list: FileList) => {
const next = Array.from(list).map((file) => ({
id: `${file.name}-${Date.now()}-${Math.random()}`,
name: file.name,
size: file.size,
status: file.size > MAX_SIZE ? ("failed" as const) : ("complete" as const),
}));
setFiles((prev) => [...prev, ...next]);
};
const removeFile = (id: string) => setFiles((prev) => prev.filter((file) => file.id !== id));
return (
Drag files here or click to browse
Each file must be 5 MB or smaller.
Select files
{files.map((file) => (
{file.name}
{file.status === "failed"
? `Exceeds the 5 MB limit (${formatBytes(file.size)})`
: formatBytes(file.size)}
{file.status === "failed" ? : null}
removeFile(file.id)} />
))}
);
}
```
### Multiple Files
Pass `multiple` to `DropZone.Input` and collect files from both the picker (`onSelect`) and drag-and-drop (`onDrop`).
```tsx
"use client";
import {DropZone} from "@darkcode-ui/react";
import React from "react";
interface UploadFile {
id: string;
name: string;
size: number;
}
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";
const units = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / 1024 ** i).toFixed(1)} ${units[i]}`;
}
function getFormat(name: string): {color: string; format: string} {
const ext = name.split(".").pop()?.toLowerCase() ?? "";
if (["png", "jpg", "jpeg", "gif", "webp"].includes(ext)) return {color: "green", format: "IMG"};
if (ext === "pdf") return {color: "red", format: "PDF"};
if (["zip", "rar", "7z"].includes(ext)) return {color: "amber", format: "ZIP"};
return {color: "blue", format: ext ? ext.toUpperCase().slice(0, 3) : "DOC"};
}
export function MultipleFiles() {
const [files, setFiles] = React.useState([]);
const addFiles = (incoming: File[]) => {
const next = incoming.map((file) => ({
id: `${file.name}-${Date.now()}-${Math.random()}`,
name: file.name,
size: file.size,
}));
setFiles((prev) => [...prev, ...next]);
};
return (
{
const dropped = await Promise.all(
event.items
.filter((item) => item.kind === "file")
.map((item) => (item as unknown as {getFile: () => Promise}).getFile()),
);
addFiles(dropped);
}}
>
Drag files here or click to browse
Upload as many files as you need.
Select files
addFiles(Array.from(list))} />
{files.map((file) => {
const meta = getFormat(file.name);
return (
{file.name}
{formatBytes(file.size)}
setFiles((prev) => prev.filter((item) => item.id !== file.id))}
/>
);
})}
);
}
```
### Custom Icon
Provide children to `DropZone.Icon` to replace the default cloud upload icon.
```tsx
import {DropZone} from "@darkcode-ui/react";
export function CustomIcon() {
return (
Drop your documents here
We accept PDF and Word files.
Browse files
);
}
```
### Custom Triggers
The Trigger, retry, and remove buttons all accept `className` and custom children for full control over their appearance.
```tsx
import {DropZone} from "@darkcode-ui/react";
export function CustomTriggers() {
return (
Drag files here or click to browse
Upload from device
Invoice.pdf
Upload failed
Retry
Remove
);
}
```
### useDropZone Hook
The `useDropZone` hook manages the whole upload lifecycle — file state, validation, image previews, and uploads — so you only render the UI. Spread `getAreaProps()` onto `DropZone.Area` and `getInputProps()` onto `DropZone.Input`, then map `files` to `DropZone.FileItem`. Rejections (wrong type, too large, too many) are reported via `onFilesRejected`.
```tsx
"use client";
import {DropZone, formatFileSize, useDropZone} from "@darkcode-ui/react";
import React from "react";
function badge(name: string): {color: string; format: string} {
const ext = name.split(".").pop()?.toUpperCase().slice(0, 4) ?? "FILE";
if (/\.(png|jpe?g|gif|webp)$/i.test(name)) return {color: "green", format: "IMG"};
if (/\.pdf$/i.test(name)) return {color: "red", format: "PDF"};
return {color: "gray", format: ext};
}
export function WithValidation() {
const [rejection, setRejection] = React.useState(null);
const dz = useDropZone({
accept: "image/*,.pdf",
maxFiles: 3,
maxSize: 2 * 1024 * 1024,
onFilesAccepted: () => setRejection(null),
onFilesRejected: (rejections) => setRejection(rejections[0]?.reason ?? null),
});
return (
Drag files here or click to browse
Images or PDF, up to 2 MB each (max 3 files).
Select files
{rejection ? {rejection}
: null}
{dz.files.map((file) => {
const meta = badge(file.name);
return (
{file.name}
{formatFileSize(file.size)}
dz.removeFile(file.id)} />
);
})}
);
}
```
### Image Previews
`DropZone.FilePreview` renders an image thumbnail from a `File` or a URL string, with automatic object-URL cleanup and a graceful fallback (a file format icon) for non-images or load errors. `useDropZone` populates `file.preview` for images automatically.
```tsx
"use client";
import {DropZone, formatFileSize, useDropZone} from "@darkcode-ui/react";
export function ImagePreview() {
const dz = useDropZone({accept: "image/*", maxFiles: 6});
return (
Drag images here or click to browse
Thumbnails are generated for each image.
Select images
{dz.files.map((file) => (
{file.name}
{formatFileSize(file.size)}
dz.removeFile(file.id)} />
))}
);
}
```
### Upload Progress
Pass `onUpload` (or `uploadUrl`) to `useDropZone` to upload each accepted file with live progress. Failed uploads surface a retry, and removing a file aborts its upload. For real uploads, use the exported `uploadFile` helper — it is `XMLHttpRequest`-based, so it reports true progress and supports `AbortSignal`.
```tsx
"use client";
import type {DropZoneFile, UseDropZoneUploadHelpers} from "@darkcode-ui/react";
import {DropZone, formatFileSize, useDropZone} from "@darkcode-ui/react";
function badge(name: string): {color: string; format: string} {
if (/\.(png|jpe?g|gif|webp)$/i.test(name)) return {color: "green", format: "IMG"};
if (/\.pdf$/i.test(name)) return {color: "red", format: "PDF"};
if (/\.(zip|rar|7z)$/i.test(name)) return {color: "amber", format: "ZIP"};
return {color: "blue", format: name.split(".").pop()?.toUpperCase().slice(0, 4) ?? "FILE"};
}
// Simulates a network upload with progress events (no backend needed). For a real
// upload, pass `uploadUrl` to useDropZone or call the exported `uploadFile` helper.
function simulateUpload(_file: DropZoneFile, {onProgress, signal}: UseDropZoneUploadHelpers) {
return new Promise((resolve, reject) => {
let progress = 0;
const interval = setInterval(() => {
if (signal.aborted) {
clearInterval(interval);
reject(new DOMException("Aborted", "AbortError"));
return;
}
progress += 8 + Math.random() * 16;
if (progress >= 100) {
clearInterval(interval);
// ~20% chance to fail so the retry affordance is visible.
if (Math.random() < 0.2) {
reject(new Error("Network error — tap retry"));
} else {
onProgress(100);
resolve();
}
} else {
onProgress(progress);
}
}, 320);
signal.addEventListener("abort", () => clearInterval(interval));
});
}
export function UploadProgress() {
const dz = useDropZone({multiple: true, onUpload: simulateUpload});
return (
Drag files here or click to browse
Uploads start automatically with live progress.
Select files
{dz.files.map((file) => {
const meta = badge(file.name);
return (
{file.name}
{file.status === "uploading"
? `Uploading… ${Math.round(file.progress)}%`
: file.status === "failed"
? file.error
: formatFileSize(file.size)}
{file.status === "uploading" ? (
) : null}
{file.status === "failed" ? (
dz.retryFile(file.id)} />
) : null}
dz.removeFile(file.id)} />
);
})}
);
}
```
### Directory & Paste
Set `acceptDirectory` on `DropZone.Input` to select an entire folder (`webkitdirectory`), or `capture` for the mobile camera. Clipboard paste (⌘/Ctrl + V) is supported automatically when the area is focused — pasted files flow through the same `onDrop` handler.
```tsx
"use client";
import {DropZone, formatFileSize, useDropZone} from "@darkcode-ui/react";
function badge(name: string): {color: string; format: string} {
if (/\.(png|jpe?g|gif|webp)$/i.test(name)) return {color: "green", format: "IMG"};
if (/\.pdf$/i.test(name)) return {color: "red", format: "PDF"};
return {color: "gray", format: name.split(".").pop()?.toUpperCase().slice(0, 4) ?? "FILE"};
}
export function DirectoryUpload() {
const dz = useDropZone({multiple: true});
return (
Choose a folder, drop files, or paste
The whole directory is added. Paste images with ⌘/Ctrl + V.
Choose folder
{dz.files.map((file) => {
const meta = badge(file.name);
return (
{file.name}
{formatFileSize(file.size)}
dz.removeFile(file.id)} />
);
})}
{dz.files.length > 0 ? (
{dz.files.length} files · {formatFileSize(dz.totalSize)}
) : null}
);
}
```
## Related Components
* **ProgressBar**: Shows determinate or indeterminate progress of an operation
* **Button**: Allows a user to perform an action
* **Label**: Accessible label for form controls
## Styling
### Customizing the component classes
To customize the DropZone component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.drop-zone__area {
@apply rounded-xl border-foreground/20;
}
.drop-zone__file-item {
@apply bg-default/40;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The DropZone component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/drop-zone.css)):
#### Base Classes
* `.drop-zone` - Root wrapper
#### Element Classes
* `.drop-zone__area` - The droppable area (RAC DropZone)
* `.drop-zone__icon` - Upload icon container
* `.drop-zone__label` - Primary label text
* `.drop-zone__description` - Secondary description text
* `.drop-zone__trigger` - Browse button
* `.drop-zone__input` - Hidden file input
* `.drop-zone__file-list` - File list container with animation
* `.drop-zone__file-item` - Individual file entry
* `.drop-zone__file-format-icon` - File type SVG icon with colored badge
* `.drop-zone__file-info` - File name and metadata container
* `.drop-zone__file-name` - File name text
* `.drop-zone__file-meta` - File size/status metadata
* `.drop-zone__file-progress` - Upload progress bar
* `.drop-zone__file-retry-trigger` - Retry button for failed uploads
* `.drop-zone__file-remove-trigger` - Remove/close button
#### Modifier Classes
* `.drop-zone__file-list--compact` - Denser file list layout
* `.drop-zone__file-format-icon--{color}` - Badge color (`gray`, `red`, `orange`, `amber`, `green`, `blue`, `purple`)
#### Interactive States
* **Drop target**: `[data-drop-target="true"]` on `.drop-zone__area` (accent border and background)
* **Focus visible**: `[data-focus-visible="true"]` on `.drop-zone__area` (focus ring)
* **Disabled**: `[data-disabled="true"]` on `.drop-zone__area` (reduced opacity)
* **File status**: `[data-status="uploading|complete|failed"]` on `.drop-zone__file-item`
## API Reference
### DropZone
The root wrapper component. Provides file picker context.
| Prop | Type | Default | Description |
| ----------- | ------------------- | ------- | ------------------------------------------------------------ |
| `children` | `ReactNode` | - | Area, FileList, and other sub-components |
| `className` | `string` | - | Additional CSS class |
| `render` | `DOMRenderFunction` | - | Custom render function to override the default `div` element |
Also supports all native `div` HTML attributes.
### DropZone.Area
The droppable area. Wraps RAC [DropZone](https://react-spectrum.adobe.com/react-aria/DropZone.html).
Also supports all RAC [DropZone](https://react-spectrum.adobe.com/react-aria/DropZone.html) props (`onDrop`, `getDropOperation`, `isDisabled`, etc.).
### DropZone.Icon
Upload icon container. Renders a default cloud upload icon when no children are provided.
### DropZone.Label
Primary label text. Wraps RAC [Text](https://react-spectrum.adobe.com/react-aria/Text.html) with `slot="label"`.
### DropZone.Description
Secondary description text.
### DropZone.Input
Hidden file input for the browse trigger.
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------ | ------- | ------------------------------------------------------------ |
| `accept` | `string` | - | Accepted file types (e.g. `"image/*,.pdf"`) |
| `multiple` | `boolean` | - | Whether multiple files can be selected |
| `acceptDirectory` | `boolean` | - | Allow selecting an entire directory (sets `webkitdirectory`) |
| `capture` | `boolean \| "user" \| "environment"` | - | Mobile capture mode for the camera/microphone |
| `onSelect` | `(files: FileList) => void` | - | Called when files are selected via the file picker |
### DropZone.Trigger
Browse button that opens the file picker. Wraps RAC [Button](https://react-spectrum.adobe.com/react-aria/Button.html). When no children are provided, it renders `Select files`.
### DropZone.FileList
Animated file list container using Motion `AnimatePresence`.
| Prop | Type | Default | Description |
| --------- | --------- | ------- | ----------------------------------------- |
| `compact` | `boolean` | `false` | Renders a denser layout for the file rows |
### DropZone.FileItem
Individual file entry with Motion layout animations.
| Prop | Type | Default | Description |
| -------- | --------------------------------------- | ------- | ------------------------------- |
| `status` | `'uploading' \| 'complete' \| 'failed'` | - | Upload status of this file item |
### DropZone.FileFormatIcon
File type SVG icon with a colored format badge.
| Prop | Type | Default | Description |
| -------- | -------- | -------- | ------------------------------------------------------------------------- |
| `format` | `string` | - | File format label (e.g. `"PDF"`, `"JPG"`) |
| `color` | `string` | `'gray'` | Badge color (`gray`, `red`, `orange`, `amber`, `green`, `blue`, `purple`) |
### DropZone.FilePreview
Image thumbnail for a file. Renders an ` ` from a `File` (creating and revoking an object URL automatically) or a URL string, and falls back to a file format icon for non-images or load errors.
| Prop | Type | Default | Description |
| ---------- | ---------------- | ----------------------------- | ------------------------------------------------------------ |
| `src` | `File \| string` | - | A `File` (object URL is managed for you) or an image URL |
| `fallback` | `ReactNode` | ` ` | Rendered when there is no preview or the image fails to load |
Also supports all native `img` HTML attributes.
### DropZone.FileInfo
Container for file name and metadata.
### DropZone.FileName
File name text display.
### DropZone.FileMeta
File size/status metadata text.
### DropZone.FileProgress
Upload progress bar. Wraps DarkCode UI [ProgressBar](/docs/react/components/progress-bar).
| Prop | Type | Default | Description |
| ------ | -------- | ------- | ----------------- |
| `size` | `string` | `'sm'` | Progress bar size |
Also supports all ProgressBar props.
### DropZone.FileProgressTrack
Progress bar track. Wraps `ProgressBar.Track`.
### DropZone.FileProgressFill
Progress bar fill. Wraps `ProgressBar.Fill`.
### DropZone.FileRetryTrigger
Retry button for failed uploads. Wraps RAC [Button](https://react-spectrum.adobe.com/react-aria/Button.html).
### DropZone.FileRemoveTrigger
Remove/close button. Wraps RAC [Button](https://react-spectrum.adobe.com/react-aria/Button.html).
## Hooks & helpers
### useDropZone
A headless hook that manages file state, validation, image previews, and uploads. Returns the file list and prop getters to wire onto the presentational parts.
```tsx
import { useDropZone } from '@darkcode-ui/react';
const dz = useDropZone({
accept: 'image/*,.pdf',
maxSize: 5 * 1024 * 1024,
maxFiles: 5,
onUpload: (file, { onProgress, signal }) => upload(file, onProgress, signal),
});
//
//
// dz.files.map(...) → dz.removeFile(id), dz.retryFile(id)
```
#### Options
| Option | Type | Default | Description |
| ------------------ | ------------------------------------------- | ---------------- | ------------------------------------------------------------------------- |
| `files` | `DropZoneFile[]` | - | Controlled file state |
| `defaultFiles` | `DropZoneFile[]` | `[]` | Initial (uncontrolled) file state |
| `onFilesChange` | `(files: DropZoneFile[]) => void` | - | Called whenever the file list changes |
| `accept` | `string` | - | Accepted file types (same syntax as the input `accept`) |
| `maxSize` | `number` | - | Maximum size per file, in bytes |
| `minSize` | `number` | - | Minimum size per file, in bytes |
| `maxFiles` | `number` | - | Maximum number of files allowed in total |
| `multiple` | `boolean` | `maxFiles !== 1` | Whether multiple files can be selected |
| `validate` | `(file: File) => string \| null` | - | Custom validator; return a message to reject |
| `disabled` | `boolean` | `false` | Disable selection, drop, and validation |
| `generatePreviews` | `boolean` | `true` | Create object-URL previews for image files |
| `onFilesAccepted` | `(files: DropZoneFile[]) => void` | - | Called with the files that were added |
| `onFilesRejected` | `(rejections: DropZoneRejection[]) => void` | - | Called with the files that failed validation |
| `onUpload` | `(file, helpers) => Promise` | - | Upload implementation per file (`helpers`: `onProgress`, `signal`) |
| `uploadUrl` | `string` | - | Convenience endpoint; uploads via `uploadFile` when `onUpload` is not set |
| `uploadOptions` | `UploadFileOptions` | - | Extra options forwarded to `uploadFile` when `uploadUrl` is used |
#### Returns
| Property | Type | Description |
| --------------- | ---------------------------------------------------- | --------------------------------------------------------- |
| `files` | `DropZoneFile[]` | Current files |
| `isDisabled` | `boolean` | Whether the drop zone is disabled |
| `totalSize` | `number` | Combined byte size of all files |
| `addFiles` | `(files: FileList \| File[]) => void` | Validate and add files |
| `removeFile` | `(id: string) => void` | Remove a file (aborts upload, revokes preview) |
| `retryFile` | `(id: string) => void` | Retry a failed/aborted upload |
| `updateFile` | `(id: string, patch: Partial) => void` | Patch a single file entry |
| `clear` | `() => void` | Remove all files |
| `getInputProps` | `() => object` | Props to spread onto `DropZone.Input` |
| `getAreaProps` | `() => object` | Props to spread onto `DropZone.Area` (also enables paste) |
`DropZoneFile` is `{ id, file, name, size, type, status, progress, error?, preview? }`, where `status` is `'pending' | 'uploading' | 'complete' | 'failed'`.
### uploadFile
`XMLHttpRequest`-based upload helper with real progress events and abort support. Pass it to `useDropZone`'s `onUpload`, or call it directly.
```tsx
import { uploadFile } from '@darkcode-ui/react';
await uploadFile(file, {
url: '/api/upload',
onProgress: ({ percent }) => setProgress(percent ?? 0),
signal: controller.signal,
});
```
| Option | Type | Default | Description |
| ----------------- | -------------------------------- | -------- | ------------------------------------------------ |
| `url` | `string` | - | Endpoint the file is uploaded to |
| `method` | `string` | `"POST"` | HTTP method |
| `fieldName` | `string` | `"file"` | Form field name for the file part |
| `headers` | `Record` | - | Additional request headers |
| `data` | `Record` | - | Extra fields appended to the `FormData` |
| `withCredentials` | `boolean` | `false` | Send cookies/credentials cross-origin |
| `signal` | `AbortSignal` | - | Cancels the in-flight upload |
| `onProgress` | `(progress) => void` | - | Progress callback (`{ loaded, total, percent }`) |
### formatFileSize
`formatFileSize(bytes, decimals?)` formats a byte count into a human-readable string (e.g. `2.4 MB`).
# ErrorMessage
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/error-message
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/error-message.mdx
> A low-level error message component for displaying errors
## Import
```tsx
import { ErrorMessage } from '@darkcode-ui/react';
```
## Usage
`ErrorMessage` is a low-level component built on React Aria's `Text` component with an `errorMessage` slot. It's designed for displaying error messages in **non-form components** such as `TagGroup`, `Calendar`, and other collection-based components.
```tsx
"use client";
import type {Key} from "@darkcode-ui/react";
import {Description, ErrorMessage, Label, Tag, TagGroup} from "@darkcode-ui/react";
import {useMemo, useState} from "react";
export function ErrorMessageBasic() {
const [selected, setSelected] = useState>(new Set());
const isInvalid = useMemo(() => Array.from(selected).length === 0, [selected]);
return (
setSelected(keys)}
>
Required Categories
News
Travel
Gaming
Shopping
Select at least one category
{!!isInvalid && <>Please select at least one category>}
);
}
```
### Anatomy
```tsx
import { TagGroup, Tag, Label, Description, ErrorMessage } from '@darkcode-ui/react';
```
## Related Components
* **TagGroup**: Focusable list of tags with selection and removal support
## When to Use
`ErrorMessage` is **not tied to forms**. It's a generic error display component for non-form contexts.
* **Recommended for** non-form components (e.g., `TagGroup`, `Calendar`, collection components)
* **For form fields**, we recommend using [`FieldError`](/docs/components/field-error) instead, which provides form-specific validation features and automatic error handling, following standardized form validation patterns.
## ErrorMessage vs FieldError
| Component | Use Case | Form Integration | Example Components |
| -------------- | ------------------------- | ---------------- | ------------------------------------ |
| `ErrorMessage` | Non-form components | No | `TagGroup`, `Calendar` |
| `FieldError` | Form fields (recommended) | Yes | `TextField`, `NumberField`, `Select` |
For form validation, we recommend using `FieldError` as it follows standardized form validation patterns and provides form-specific features. See the [FieldError documentation](/docs/components/field-error) and the [Form guide](/docs/components/form) for examples and best practices.
## API Reference
### ErrorMessage Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The error message content |
**Note**: `ErrorMessage` is built on React Aria's `Text` component with `slot="errorMessage"`. It can be targeted using the `[slot=errorMessage]` CSS selector.
## Accessibility
The ErrorMessage component enhances accessibility by:
* Using semantic HTML that screen readers can identify
* Providing the `slot="errorMessage"` attribute for React Aria integration
* Supporting proper text contrast ratios for error states
* Following WAI-ARIA best practices for error messaging
## Styling
### Passing Tailwind CSS classes
```tsx
import { ErrorMessage } from '@darkcode-ui/react';
function CustomErrorMessage() {
return (
Custom styled error message
);
}
```
### Customizing the component classes
To customize the ErrorMessage component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.error-message {
@apply text-red-600 text-sm font-medium;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The ErrorMessage component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/error-message.css)):
#### Base Classes
* `.error-message` - Base error message styles with danger color and text truncation
#### Slot Classes
* `[slot="errorMessage"]` - ErrorMessage slot styles for React Aria integration
# FieldError
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/field-error
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/field-error.mdx
> Displays validation error messages for form fields
## Import
```tsx
import { FieldError } from '@darkcode-ui/react';
```
## Usage
The FieldError component displays validation error messages for form fields. It automatically appears when the parent field is marked as invalid and provides smooth opacity transitions.
```tsx
"use client";
import {FieldError, Input, Label, TextField} from "@darkcode-ui/react";
import {useState} from "react";
export function Basic() {
const [value, setValue] = useState("jr");
const isInvalid = value.length > 0 && value.length < 3;
return (
Username
setValue(e.target.value)}
/>
Username must be at least 3 characters
);
}
```
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **Input**: Single-line text input built on React Aria
* **TextArea**: Multiline text input with focus management
## API
### FieldError Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------ | ------- | ---------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| ((validation: ValidationResult) => ReactNode)` | - | Error message content or render function |
## Accessibility
The FieldError component ensures accessibility by:
* Using proper ARIA attributes for error announcement
* Supporting screen readers with semantic HTML
* Providing visual and programmatic error indication
* Automatically managing visibility based on validation state
## Styling
The FieldError component uses the following CSS classes:
* `.field-error` - Base error styles with danger color
* Only shows when the `data-visible` attribute is present
* Text is truncated with ellipsis for long messages
## Examples
### Basic Validation
```tsx
export function Basic() {
const [value, setValue] = useState("");
const isInvalid = value.length > 0 && value.length < 3;
return (
Username
setValue(e.target.value)}
/>
Username must be at least 3 characters
);
}
```
### With Dynamic Messages
```tsx
0}>
Password
{(validation) => validation.validationErrors.join(', ')}
```
### Custom Validation Logic
```tsx
function EmailField() {
const [email, setEmail] = useState('');
const isInvalid = email.length > 0 && !email.includes('@');
return (
Email
setEmail(e.target.value)}
/>
Email must include @ symbol
);
}
```
### Multiple Error Messages
```tsx
Username
{errors.map((error, i) => (
{error}
))}
```
# Fieldset
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/fieldset
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/fieldset.mdx
> Group related form controls with legends, descriptions, and actions
## Import
```tsx
import { Fieldset } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {
Button,
Description,
FieldError,
FieldGroup,
Fieldset,
Form,
Input,
Label,
TextArea,
TextField,
} from "@darkcode-ui/react";
import {FloppyDisk} from "@gravity-ui/icons";
export function Basic() {
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
// Convert FormData to plain object
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert("Form submitted successfully!");
};
return (
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` on form controls (Input, TextArea, etc.) to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {
Button,
Description,
FieldError,
Fieldset,
Form,
Input,
Label,
Surface,
TextArea,
TextField,
} from "@darkcode-ui/react";
import {FloppyDisk} from "@gravity-ui/icons";
import React from "react";
export function OnSurface() {
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
// Convert FormData to plain object
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert("Form submitted successfully!");
};
return (
Profile Settings
Update your profile information.
{
if (value.length < 3) {
return "Name must be at least 3 characters";
}
return null;
}}
>
Name
Email
{
if (value.length < 10) {
return "Bio must be at least 10 characters";
}
return null;
}}
>
Bio
Minimum 10 characters
Save changes
Cancel
);
}
```
### Anatomy
Import the Fieldset component and access all parts using dot notation.
```tsx
import { Fieldset } from '@darkcode-ui/react';
export default () => (
{/* form fields go here */}
{/* action buttons go here */}
)
```
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **Label**: Accessible label for form controls
* **CheckboxGroup**: Group of checkboxes with shared state
## Styling
### Passing Tailwind CSS classes
```tsx
import { Fieldset, TextField, Label, Input } from '@darkcode-ui/react';
function CustomFieldset() {
return (
Team members
First name
Last name
{/* Action buttons */}
);
}
```
### Customizing the component classes
Use the `@layer components` directive to target Fieldset [BEM](https://getbem.com/)-style classes.
```css
@layer components {
.fieldset {
@apply gap-5 rounded-xl border border-border/60 bg-surface p-6 shadow-field;
}
.fieldset__legend {
@apply text-lg font-semibold;
}
.fieldset__field_group {
@apply gap-3 md:grid md:grid-cols-2;
}
.fieldset__actions {
@apply flex justify-end gap-2 pt-2;
}
}
```
### CSS Classes
The Fieldset compound component exposes these CSS selectors:
* `.fieldset` – Root container
* `.fieldset__legend` – Legend element
* `.fieldset__field_group` – Wrapper for grouped fields
* `.fieldset__actions` – Action bar below the fields
## API Reference
### Fieldset Props
| Prop | Type | Default | Description |
| ------------- | ------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------- |
| `className` | `string` | - | Tailwind CSS classes applied to the root element. |
| `children` | `React.ReactNode` | - | Fieldset content (legend, groups, descriptions, actions). |
| `nativeProps` | `React.HTMLAttributes` | Supports native fieldset attributes and events. | |
### Fieldset.Legend Props
| Prop | Type | Default | Description |
| ------------- | ----------------------------------------- | ------- | ---------------------------------------- |
| `className` | `string` | - | Tailwind classes for the legend element. |
| `children` | `React.ReactNode` | - | Legend content, usually plain text. |
| `nativeProps` | `React.HTMLAttributes` | - | Native legend attributes. |
### Fieldset.Group Props
| Prop | Type | Default | Description |
| ------------- | -------------------------------------- | ------- | ---------------------------------------------- |
| `className` | `string` | - | Layout and spacing classes for grouped fields. |
| `children` | `React.ReactNode` | - | Form controls to group inside the fieldset. |
| `nativeProps` | `React.HTMLAttributes` | - | Native div attributes. |
### Fieldset.Actions Props
| Prop | Type | Default | Description |
| ------------- | -------------------------------------- | ------- | ------------------------------------------------- |
| `className` | `string` | - | Tailwind classes to align action buttons or text. |
| `children` | `React.ReactNode` | - | Action buttons or helper text. |
| `nativeProps` | `React.HTMLAttributes` | - | Native div attributes. |
# Form
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/form
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/form.mdx
> Wrapper component for form validation and submission handling
## Import
```tsx
import { Form } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {Button, Description, FieldError, Form, Input, Label, TextField} from "@darkcode-ui/react";
import {Check} from "@gravity-ui/icons";
export function Basic() {
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
// Convert FormData to plain object
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert(`Form submitted with: ${JSON.stringify(data, null, 2)}`);
};
return (
{
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return "Please enter a valid email address";
}
return null;
}}
>
Email
{
if (value.length < 8) {
return "Password must be at least 8 characters";
}
if (!/[A-Z]/.test(value)) {
return "Password must contain at least one uppercase letter";
}
if (!/[0-9]/.test(value)) {
return "Password must contain at least one number";
}
return null;
}}
>
Password
Must be at least 8 characters with 1 uppercase and 1 number
Submit
Reset
);
}
```
### Anatomy
Import all parts and piece them together.
```tsx
import {Form, Button} from '@darkcode-ui/react';
export default () => (
{/* Form fields go here */}
)
```
### Custom Render Function
```tsx
"use client";
import {Button, Description, FieldError, Form, Input, Label, TextField} from "@darkcode-ui/react";
import {Check} from "@gravity-ui/icons";
export function CustomRenderFunction() {
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
// Convert FormData to plain object
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert(`Form submitted with: ${JSON.stringify(data, null, 2)}`);
};
return (
}
onSubmit={onSubmit}
>
{
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return "Please enter a valid email address";
}
return null;
}}
>
Email
{
if (value.length < 8) {
return "Password must be at least 8 characters";
}
if (!/[A-Z]/.test(value)) {
return "Password must contain at least one uppercase letter";
}
if (!/[0-9]/.test(value)) {
return "Password must contain at least one number";
}
return null;
}}
>
Password
Must be at least 8 characters with 1 uppercase and 1 number
Submit
Reset
);
}
```
## Related Components
* **Button**: Allows a user to perform an action
* **Fieldset**: Group related form controls with legends
* **TextField**: Composition-friendly fields with labels and validation
## Styling
### Passing Tailwind CSS classes
```tsx
import {Form, TextField, Label, Input, FieldError, Button} from '@darkcode-ui/react';
function CustomForm() {
return (
Email
Submit
);
}
```
## API Reference
### Form Props
The Form component is a wrapper around React Aria's Form primitive that provides form validation and submission handling capabilities.
| Prop | Type | Default | Description |
| -------------------- | ------------------------------------------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action` | `string \| FormHTMLAttributes['action']` | - | The URL to submit the form data to. |
| `className` | `string` | - | Tailwind CSS classes applied to the form element. |
| `children` | `React.ReactNode` | - | Form content (fields, buttons, etc.). |
| `encType` | `'application/x-www-form-urlencoded' \| 'multipart/form-data' \| 'text/plain'` | - | The encoding type for form data submission. |
| `method` | `'get' \| 'post'` | - | The HTTP method to use when submitting the form. |
| `onInvalid` | `(event: FormEvent) => void` | - | Handler called when the form validation fails. By default, the first invalid field will be focused. Use `preventDefault()` to customize focus behavior. |
| `onReset` | `(event: FormEvent) => void` | - | Handler called when the form is reset. |
| `onSubmit` | `(event: FormEvent) => void` | - | Handler called when the form is submitted. |
| `target` | `'_self' \| '_blank' \| '_parent' \| '_top'` | - | Where to display the response after submitting the form. |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML validation or ARIA validation. 'native' blocks form submission, 'aria' displays errors in realtime. |
| `validationErrors` | `ValidationErrors` | - | Server-side validation errors mapped by field name. Displayed immediately and cleared when user modifies the field. |
| `aria-label` | `string` | - | Accessibility label for the form. |
| `aria-labelledby` | `string` | - | ID of element that labels the form. Creates a form landmark when provided. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Form Validation
The Form component integrates with React Aria's validation system, allowing you to:
* Use built-in HTML5 validation attributes (`required`, `minLength`, `pattern`, etc.)
* Provide custom validation functions on TextField components
* Display validation errors with FieldError components
* Handle form submission with proper validation
* Provide server-side validation errors via `validationErrors` prop
#### Validation Behavior
The `validationBehavior` prop controls how validation is displayed:
* **`native`** (default): Uses native HTML validation, blocks form submission on errors
* **`aria`**: Uses ARIA attributes for validation, displays errors in realtime as user types, doesn't block submission
This behavior can be set at the form level or overridden at individual field level.
### Form Submission
Forms can be submitted in several ways:
* **Traditional submission**: Set the `action` prop to submit to a URL
* **JavaScript handling**: Use the `onSubmit` handler to process form data
* **FormData API**: Access form data using the FormData API in your submit handler
Example with FormData:
```tsx
function handleSubmit(e: FormEvent) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData);
console.log('Form data:', data);
}
```
### Integration with Form Fields
The Form component works seamlessly with DarkCode UI's form field components:
* **TextField**: For text inputs with labels and validation
* **Checkbox**: For boolean selections
* **RadioGroup**: For single selection from multiple options
* **Switch**: For toggle controls
* **Button**: For form submission and reset actions
All field components automatically integrate with the Form's validation and submission behavior when placed inside it.
### Accessibility
Forms are accessible by default when using React Aria components. Key features include:
* Native `` element semantics
* Form landmark creation with `aria-label` or `aria-labelledby`
* Automatic focus management on validation errors
* ARIA validation attributes when using `validationBehavior="aria"`
### Advanced Usage
For more advanced use cases including:
* Custom validation context
* Form context providers
* Integration with third-party libraries
* Custom focus management on validation errors
Please refer to the [React Aria Form documentation](https://react-spectrum.adobe.com/react-aria/Form.html).
# Inline Select
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/inline-select
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/inline-select.mdx
> A minimal inline dropdown that blends into surrounding text for contextual selections without a full form field
## Import
```tsx
import { InlineSelect } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {InlineSelect, ListBox} from "@darkcode-ui/react";
export function InlineSelectDefault() {
return (
Show me results from the past{" "}
24 hours
7 days
30 days
90 days
{" "}
across all projects.
);
}
```
### Anatomy
Import the InlineSelect component and access all parts using dot notation.
```tsx
import { InlineSelect, ListBox } from '@darkcode-ui/react';
export default () => (
7 days
)
```
InlineSelect is a ghost-styled wrapper around the DarkCode UI [Select](/docs/react/components/select).
It inherits all of Select's props and accessibility behavior — only the visual styling changes so
it blends into surrounding text.
### Sizes
Use the `size` prop (`sm`, `md`, `lg`) to scale the text, indicator, padding, and value width.
```tsx
"use client";
import {InlineSelect, ListBox} from "@darkcode-ui/react";
const Options = () => (
24 hours
7 days
30 days
);
export function InlineSelectSizes() {
return (
{(["sm", "md", "lg"] as const).map((size) => (
))}
);
}
```
### Emphasis
Use the `variant` prop to control how the trigger reads inline: `default` (foreground),
`muted` (subtle, brightening on hover), or `accent` (a colored, link-like control).
```tsx
"use client";
import {InlineSelect, ListBox} from "@darkcode-ui/react";
const Options = () => (
24 hours
7 days
30 days
);
export function InlineSelectEmphasis() {
return (
{(["default", "muted", "accent"] as const).map((variant) => (
Show the last{" "}
{" "}
of activity.
))}
);
}
```
### Team Switcher
```tsx
"use client";
import {Avatar, AvatarFallback, InlineSelect, ListBox} from "@darkcode-ui/react";
const teams = [
{id: "acme", initials: "AC", name: "Acme Inc."},
{id: "globex", initials: "GX", name: "Globex Corp."},
{id: "umbrella", initials: "UM", name: "Umbrella LLC"},
];
export function InlineSelectTeamSwitcher() {
return (
AC
{teams.map((team) => (
{team.initials}
{team.name}
))}
);
}
```
### Custom Indicator
Pass children to `InlineSelect.Indicator` to replace the default `ChevronsExpandVertical` icon.
```tsx
"use client";
import {InlineSelect, ListBox} from "@darkcode-ui/react";
import {ChevronDown} from "@gravity-ui/icons";
export function InlineSelectCustomIndicator() {
return (
Newest first
Oldest first
Most popular
);
}
```
### Multi Select
Set `selectionMode="multiple"` on both the root and the `ListBox` to allow multiple selections.
```tsx
"use client";
import {InlineSelect, ListBox} from "@darkcode-ui/react";
export function InlineSelectMultiSelect() {
return (
Design
Engineering
Marketing
Sales
);
}
```
### Loading
Pass `isLoading` to swap the indicator for a spinner while options load (e.g. with an async list).
```tsx
"use client";
import {InlineSelect, ListBox} from "@darkcode-ui/react";
export function InlineSelectLoading() {
return (
Acme
Globex
);
}
```
### Placements
Control where the dropdown opens with the `placement` prop on `InlineSelect.Popover`
(defaults to `bottom end`).
```tsx
"use client";
import {InlineSelect, ListBox} from "@darkcode-ui/react";
const placements = ["bottom start", "bottom end", "top start", "top end"] as const;
export function InlineSelectPlacements() {
return (
{placements.map((placement) => (
{placement} · One
{placement} · Two
))}
);
}
```
## Styling
### Customizing the component classes
To customize the InlineSelect component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.inline-select {
--inline-select-value-max-width: 16rem;
--inline-select-indicator-size: 0.875rem;
}
.inline-select__trigger {
@apply font-semibold;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The InlineSelect component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/inline-select.css)):
#### Base & Variant Classes
* `.inline-select` - Root wrapper with custom properties
* `.inline-select--sm|lg` - Size variants (`md` is the default and lives in the base)
* `.inline-select--default|muted|accent` - Emphasis variants (set the trigger text color)
* `.inline-select__trigger` - Ghost-styled trigger button (overrides Select field styles)
* `.inline-select__value` - Displayed text of the selected item(s)
* `.inline-select__indicator` - Inline chevron icon (static positioning instead of absolute)
* `.inline-select__popover` - Dropdown panel
#### Interactive States
* **Hover**: `:hover` or `[data-hovered="true"]` on `.inline-select__trigger` (text color change)
* **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]` on `.inline-select__trigger` (focus ring)
* **Disabled**: `:disabled` or `[aria-disabled="true"]` on `.inline-select__trigger` (reduced opacity)
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` on `.inline-select` (danger text)
* **Open**: `[data-open="true"]` on `.inline-select__indicator`
#### CSS Variables
* `--inline-select-value-max-width` - Max width of the value text (default: `12rem`)
* `--inline-select-indicator-size` - Size of the indicator icon (default: `0.75rem`)
* `--inline-select-fg` / `--inline-select-fg-hover` - Trigger text color (set by the emphasis variants)
## API Reference
### InlineSelect
The root component. Wraps the DarkCode UI [Select](/docs/react/components/select) with ghost styling for inline use.
| Prop | Type | Default | Description |
| ----------- | ---------------------------------- | ----------- | ------------------------------------------------------------ |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Scales the text, indicator, padding, and value width. |
| `variant` | `"default" \| "muted" \| "accent"` | `"default"` | Emphasis of the trigger text. |
| `isLoading` | `boolean` | `false` | Show a spinner in place of the indicator while options load. |
| `children` | `ReactNode` | - | Trigger, Value, Indicator, Popover, and list items |
Also supports all [DarkCode UI Select](/docs/react/components/select) props (`isDisabled`, `isInvalid`, `selectionMode`, etc.).
### InlineSelect.Trigger
Ghost-styled trigger button. Also supports all DarkCode UI `Select.Trigger` props.
### InlineSelect.Value
Displayed text of the selected item(s), truncated. Also supports all DarkCode UI `Select.Value` props.
### InlineSelect.Indicator
Chevron icon. Defaults to a `ChevronsExpandVertical` icon when no children are provided.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | --------------------- |
| `children` | `ReactNode` | - | Custom indicator icon |
Also supports all DarkCode UI `Select.Indicator` props.
### InlineSelect.Popover
Dropdown panel for selection options.
| Prop | Type | Default | Description |
| ----------- | ----------- | -------------- | ----------------------------------------- |
| `placement` | `Placement` | `'bottom end'` | Popover placement relative to the trigger |
Also supports all DarkCode UI `Select.Popover` props.
## Accessibility
InlineSelect is built on the DarkCode UI Select (React Aria Components `Select`), so it provides:
* Full keyboard navigation and type-ahead
* Correct ARIA roles and `aria-expanded` / `aria-current` wiring
* Screen reader announcements for the selected value
* Always provide an `aria-label` (or associate a ``) since inline selects rarely have a visible field label
# InputGroup
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/input-group
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/input-group.mdx
> Group related input controls with prefix and suffix elements for enhanced form fields
## Import
```tsx
import { InputGroup } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function Default() {
return (
Email address
);
}
```
### Anatomy
```tsx
import {InputGroup, TextField, Label} from '@darkcode-ui/react';
export default () => (
{/* Or use InputGroup.TextArea for multiline input */}
)
```
> **InputGroup** wraps an input field with optional prefix and suffix elements, creating a visually cohesive group. It's typically used within **[TextField](/docs/components/text-field)** to add icons, text, buttons, or other elements before or after the input. Use **InputGroup.Input** for single-line inputs or **InputGroup.TextArea** for multiline text inputs.
### With Prefix Icon
Add an icon before the input field.
```tsx
"use client";
import {Description, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function WithPrefixIcon() {
return (
Email address
We'll never share this with anyone else
);
}
```
### With Suffix Icon
Add an icon after the input field.
```tsx
"use client";
import {Description, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function WithSuffixIcon() {
return (
Email address
We don't send spam
);
}
```
### With Prefix and Suffix
Combine both prefix and suffix elements.
```tsx
"use client";
import {Description, InputGroup, Label, TextField} from "@darkcode-ui/react";
export function WithPrefixAndSuffix() {
return (
Set a price
$
USD
What customers would pay
);
}
```
### Text Prefix
Use text as a prefix, such as currency symbols or protocol prefixes.
```tsx
"use client";
import {InputGroup, Label, TextField} from "@darkcode-ui/react";
export function WithTextPrefix() {
return (
Website
https://
);
}
```
### Text Suffix
Use text as a suffix, such as domain extensions or units.
```tsx
"use client";
import {InputGroup, Label, TextField} from "@darkcode-ui/react";
export function WithTextSuffix() {
return (
Website
.com
);
}
```
### Icon Prefix and Text Suffix
Combine an icon prefix with a text suffix.
```tsx
"use client";
import {InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Globe} from "@gravity-ui/icons";
export function WithIconPrefixAndTextSuffix() {
return (
Website
.com
);
}
```
### Copy Button Suffix
Add an interactive button in the suffix, such as a copy button.
```tsx
"use client";
import {Button, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Copy} from "@gravity-ui/icons";
export function WithCopySuffix() {
return (
Website
);
}
```
### Icon Prefix and Copy Button
Combine an icon prefix with an interactive button suffix.
```tsx
"use client";
import {Button, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Copy, Globe} from "@gravity-ui/icons";
export function WithIconPrefixAndCopySuffix() {
return (
Website
);
}
```
### Password Toggle
Use a button in the suffix to toggle password visibility.
```tsx
"use client";
import {Button, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Eye, EyeSlash} from "@gravity-ui/icons";
import {useState} from "react";
export function PasswordWithToggle() {
const [isVisible, setIsVisible] = useState(false);
return (
Password
setIsVisible(!isVisible)}
>
{isVisible ? : }
);
}
```
### Loading State
Show a loading spinner in the suffix to indicate processing.
```tsx
"use client";
import {InputGroup, Spinner, TextField} from "@darkcode-ui/react";
export function WithLoadingSuffix() {
return (
);
}
```
### Keyboard Shortcut
Display keyboard shortcuts using the [Kbd](/docs/components/kbd) component.
```tsx
"use client";
import {InputGroup, Kbd, TextField} from "@darkcode-ui/react";
export function WithKeyboardShortcut() {
return (
K
);
}
```
### Badge Suffix
Add a badge or chip in the suffix to show status or labels.
```tsx
"use client";
import {Chip, InputGroup, TextField} from "@darkcode-ui/react";
export function WithBadgeSuffix() {
return (
Pro
);
}
```
### Required Field
InputGroup respects the required state from its parent TextField.
```tsx
"use client";
import {Description, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function Required() {
return (
Email address
Set a price
$
USD
What customers would pay
);
}
```
### Validation
InputGroup automatically reflects invalid state from its parent TextField.
```tsx
"use client";
import {FieldError, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function Invalid() {
return (
Email address
Please enter a valid email address
Set a price
$
USD
Price must be greater than 0
);
}
```
### Disabled State
InputGroup respects the disabled state from its parent TextField.
```tsx
"use client";
import {InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function Disabled() {
return (
Email address
Set a price
$
USD
);
}
```
### Full Width
```tsx
import {InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope, Eye} from "@gravity-ui/icons";
export function FullWidth() {
return (
Email address
Password
);
}
```
### Variants
The InputGroup component supports two visual variants:
* **`primary`** (default) - Standard styling with shadow, suitable for most use cases
* **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
import {InputGroup, Label, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {Description, InputGroup, Label, Surface, TextField} from "@darkcode-ui/react";
import {Envelope} from "@gravity-ui/icons";
export function OnSurface() {
return (
Email address
We'll never share this with anyone else
);
}
```
### With TextArea
Use **InputGroup.TextArea** for multiline text inputs with prefix and suffix elements. When a textarea is present, the container automatically adjusts its height to accommodate the content and aligns prefix/suffix elements to the top.
```tsx
"use client";
import {Button, InputGroup, Kbd, Spinner, TextField, Tooltip} from "@darkcode-ui/react";
import {ArrowUp, At, Microphone, PlugConnection, Plus} from "@gravity-ui/icons";
import {useState} from "react";
export function WithTextArea() {
const [value, setValue] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = () => {
if (!value.trim()) return;
setIsSubmitting(true);
setTimeout(() => {
setIsSubmitting(false);
setValue("");
}, 1000);
};
return (
Add Context
setValue(event.target.value)}
/>
Add a files and more
Connect apps
Voice input
{({isPending}) => (isPending ? : )}
Send
);
}
```
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **Input**: Single-line text input built on React Aria
* **Label**: Accessible label for form controls
## Styling
### Passing Tailwind CSS classes
```tsx
import {InputGroup, TextField, Label} from '@darkcode-ui/react';
function CustomInputGroup() {
return (
Website
https://
.com
);
}
```
### Customizing the component classes
InputGroup uses CSS classes that can be customized. Override the component classes to match your design system.
```css
@layer components {
.input-group {
@apply bg-field text-field-foreground shadow-field rounded-field inline-flex min-h-9 items-center overflow-hidden border text-sm outline-none;
}
.input-group__input {
@apply flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
}
.input-group__prefix {
@apply text-field-placeholder rounded-l-field flex h-full items-center justify-center rounded-r-none bg-transparent px-3;
}
.input-group__suffix {
@apply text-field-placeholder rounded-r-field flex h-full items-center justify-center rounded-l-none bg-transparent px-3;
}
/* Secondary variant */
.input-group--secondary {
@apply shadow-none;
background-color: var(--color-default);
}
}
```
### CSS Classes
* `.input-group` – Root container with border, background, and flex layout. Uses `min-h-9` for flexible height and `items-center` by default, switching to `items-start` when a textarea is present.
* `.input-group__input` – Input element with transparent background and no border. Also used as the base class for textarea elements.
* `.input-group__prefix` – Prefix container with left border radius. Aligns to top when used with textarea.
* `.input-group__suffix` – Suffix container with right border radius. Aligns to top when used with textarea.
* `.input-group--primary` – Primary variant with shadow (default)
* `.input-group--secondary` – Secondary variant without shadow, suitable for use in surfaces
**Note**: When using `InputGroup.TextArea`, the container automatically switches from `items-center` to `items-start` alignment and uses `height: auto` instead of a fixed height. Prefix and suffix elements align to the top with additional padding to match the textarea's vertical padding. The textarea uses the same `.input-group__input` base class with textarea-specific styles (minimum height and vertical resize) applied via the `[data-slot="input-group-textarea"]` attribute selector.
### Interactive States
InputGroup automatically manages these data attributes based on its state:
* **Hover**: `[data-hovered]` - Applied when hovering over the group
* **Focus Within**: `[data-focus-within]` - Applied when the input is focused
* **Invalid**: `[data-invalid]` - Applied when parent TextField is invalid
* **Disabled**: `[data-disabled]` or `[aria-disabled]` - Applied when parent TextField is disabled
## API Reference
### InputGroup Props
InputGroup inherits all props from React Aria's [Group](https://react-spectrum.adobe.com/react-aria/Group.html) component.
#### Base Props
| Prop | Type | Default | Description |
| ----------- | -------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: GroupRenderProps) => React.ReactNode` | - | Child components (Input, TextArea, Prefix, Suffix) or render function. |
| `className` | `string \| (values: GroupRenderProps) => string` | - | CSS classes for styling, supports render props. |
| `style` | `React.CSSProperties \| (values: GroupRenderProps) => React.CSSProperties` | - | Inline styles, supports render props. |
| `fullWidth` | `boolean` | `false` | Whether the input group should take full width of its container |
| `id` | `string` | - | The element's unique identifier. |
#### Variant Props
| Prop | Type | Default | Description |
| --------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
#### Accessibility Props
| Prop | Type | Default | Description |
| ------------------ | --------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------- |
| `aria-label` | `string` | - | Accessibility label when no visible label is present. |
| `aria-labelledby` | `string` | - | ID of elements that label this group. |
| `aria-describedby` | `string` | - | ID of elements that describe this group. |
| `aria-details` | `string` | - | ID of elements with additional details. |
| `role` | `'group' \| 'region' \| 'presentation'` | `'group'` | Accessibility role for the group. Use 'region' for important content, 'presentation' for visual-only grouping. |
### Composition Components
InputGroup works with these subcomponents:
* **InputGroup.Root** - Root container (also available as `InputGroup`)
* **InputGroup.Input** - Single-line input element component
* **InputGroup.TextArea** - Multiline textarea element component
* **InputGroup.Prefix** - Prefix container component
* **InputGroup.Suffix** - Suffix container component
#### InputGroup.Input Props
InputGroup.Input inherits all props from React Aria's [Input](https://react-spectrum.adobe.com/react-aria/Input.html) component.
| Prop | Type | Default | Description |
| -------------- | -------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `className` | `string` | - | CSS classes for styling. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the input. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `type` | `string` | `'text'` | Input type (text, password, email, etc.). |
| `value` | `string` | - | Current value (controlled). |
| `defaultValue` | `string` | - | Default value (uncontrolled). |
| `placeholder` | `string` | - | Placeholder text. |
| `disabled` | `boolean` | - | Whether the input is disabled. |
| `readOnly` | `boolean` | - | Whether the input is read-only. |
#### InputGroup.TextArea Props
InputGroup.TextArea inherits all props from React Aria's [TextArea](https://react-spectrum.adobe.com/react-aria/TextArea.html) component.
| Prop | Type | Default | Description |
| -------------- | -------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `className` | `string` | - | CSS classes for styling. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the textarea. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `value` | `string` | - | Current value (controlled). |
| `defaultValue` | `string` | - | Default value (uncontrolled). |
| `placeholder` | `string` | - | Placeholder text. |
| `rows` | `number` | - | Number of visible text lines. |
| `disabled` | `boolean` | - | Whether the textarea is disabled. |
| `readOnly` | `boolean` | - | Whether the textarea is read-only. |
#### InputGroup.Prefix Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ----------------------------------------------------- |
| `children` | `React.ReactNode` | - | Content to display in the prefix (icons, text, etc.). |
| `className` | `string` | - | CSS classes for styling. |
#### InputGroup.Suffix Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | Content to display in the suffix (icons, buttons, badges, etc.). |
| `className` | `string` | - | CSS classes for styling. |
### Usage Example
```tsx
import {InputGroup, TextField, Label, Button} from '@darkcode-ui/react';
import {Icon} from '@iconify/react';
function Example() {
return (
Email
);
}
```
### TextArea Usage Example
```tsx
import {Envelope} from "@gravity-ui/icons";
import {Description, FieldError, InputGroup, Label, TextField} from "@darkcode-ui/react";
import {useState} from "react";
function TextAreaExample() {
const [feedback, setFeedback] = useState("");
return (
500} name="feedback" onChange={setFeedback}>
Your Feedback
Maximum 500 characters.
{feedback.length}/500
Feedback must be less than 500 characters
);
}
```
# InputOTP
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/input-otp
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/input-otp.mdx
> A one-time password input component for verification codes and secure authentication
## Import
```tsx
import { InputOTP } from '@darkcode-ui/react';
```
### Usage
```tsx
import {InputOTP, Label, Link} from "@darkcode-ui/react";
export function Basic() {
return (
Verify account
We've sent a code to a****@gmail.com
Didn't receive a code?
Resend
);
}
```
### Anatomy
Import the InputOTP component and access all parts using dot notation.
```tsx
import { InputOTP } from '@darkcode-ui/react';
export default () => (
{/* ...rest of the slots */}
{/* ...rest of the slots */}
)
```
> **InputOTP** is built on top of [input-otp](https://github.com/guilhermerodz/input-otp) by [@guilherme\_rodz](https://twitter.com/guilherme_rodz), providing a flexible and accessible foundation for OTP input components.
### Four Digits
```tsx
import {InputOTP, Label} from "@darkcode-ui/react";
export function FourDigits() {
return (
Enter PIN
);
}
```
### Disabled State
```tsx
import {Description, InputOTP, Label} from "@darkcode-ui/react";
export function Disabled() {
return (
Verify account
Code verification is currently disabled
);
}
```
### With Pattern
Use the `pattern` prop to restrict input to specific characters. DarkCode UI exports common patterns like `REGEXP_ONLY_CHARS` and `REGEXP_ONLY_DIGITS`.
```tsx
import {Description, InputOTP, Label, REGEXP_ONLY_CHARS} from "@darkcode-ui/react";
export function WithPattern() {
return (
Enter code (letters only)
Only alphabetic characters are allowed
);
}
```
### Controlled
Control the value to synchronize with state, clear the input, or implement custom validation.
```tsx
"use client";
import {Description, InputOTP, Label} from "@darkcode-ui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("");
return (
Verify account
{value.length > 0 ? (
<>
Value: {value} ({value.length}/6) •{" "}
setValue("")}>
Clear
>
) : (
"Enter a 6-digit code"
)}
);
}
```
### With Validation
Use `isInvalid` together with validation messages to surface errors.
```tsx
"use client";
import {Button, Description, Form, InputOTP, Label} from "@darkcode-ui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState("");
const [isInvalid, setIsInvalid] = React.useState(false);
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const code = formData.get("code");
if (code !== "123456") {
setIsInvalid(true);
return;
}
setIsInvalid(false);
setValue("");
alert("Code verified successfully!");
};
const handleChange = (val: string) => {
setValue(val);
setIsInvalid(false);
};
return (
Verify account
Hint: The code is 123456
Invalid code. Please try again.
Submit
);
}
```
### On Complete
Use the `onComplete` callback to trigger actions when all slots are filled.
```tsx
"use client";
import {Button, Form, InputOTP, Label, Spinner} from "@darkcode-ui/react";
import React from "react";
export function OnComplete() {
const [value, setValue] = React.useState("");
const [isComplete, setIsComplete] = React.useState(false);
const [isSubmitting, setIsSubmitting] = React.useState(false);
const handleComplete = (code: string) => {
setIsComplete(true);
console.log("Code complete:", code);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
setIsSubmitting(false);
setValue("");
setIsComplete(false);
}, 2000);
};
return (
Verify account
{
setValue(val);
setIsComplete(false);
}}
>
{isSubmitting ? (
<>
Verifying...
>
) : (
"Verify Code"
)}
);
}
```
### Form Example
A complete two-factor authentication form with validation and submission.
```tsx
"use client";
import {Button, Description, Form, InputOTP, Label, Link, Spinner} from "@darkcode-ui/react";
import React from "react";
export function FormExample() {
const [value, setValue] = React.useState("");
const [error, setError] = React.useState("");
const [isSubmitting, setIsSubmitting] = React.useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError("");
if (value.length !== 6) {
setError("Please enter all 6 digits");
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
if (value === "123456") {
console.log("Code verified successfully!");
setValue("");
} else {
setError("Invalid code. Please try again.");
}
setIsSubmitting(false);
}, 1500);
};
return (
Two-factor authentication
Enter the 6-digit code from your authenticator app
{
setValue(val);
setError("");
}}
>
{error}
{isSubmitting ? (
<>
Verifying...
>
) : (
"Verify"
)}
Having trouble?
Use backup code
);
}
```
### Variants
The InputOTP component supports two visual variants:
* **`primary`** (default) - Standard styling with shadow, suitable for most use cases
* **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
import {InputOTP, Label} from "@darkcode-ui/react";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {InputOTP, Label, Link, Surface} from "@darkcode-ui/react";
export function OnSurface() {
return (
Verify account
We've sent a code to a****@gmail.com
Didn't receive a code?
Resend
);
}
```
## Related Components
* **Input**: Single-line text input built on React Aria
* **Form**: Form validation and submission handling
* **Surface**: Base container surface
## Styling
### Passing Tailwind CSS classes
```tsx
import {InputOTP, Label} from '@darkcode-ui/react';
function CustomInputOTP() {
return (
Enter verification code
);
}
```
### Customizing the component classes
To customize the InputOTP component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.input-otp {
@apply gap-3;
}
.input-otp__slot {
@apply size-12 rounded-xl border-2 font-bold;
}
.input-otp__slot[data-active="true"] {
@apply border-primary-500 ring-2 ring-primary-200;
}
.input-otp__separator {
@apply w-2 h-1 bg-border-strong rounded-full;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The InputOTP component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/input-otp.css)):
#### Base Classes
* `.input-otp` - Base container
* `.input-otp__container` - Inner container from input-otp library
* `.input-otp__group` - Group of slots
* `.input-otp__slot` - Individual input slot
* `.input-otp__slot-value` - The character inside a slot
* `.input-otp__caret` - Blinking caret indicator
* `.input-otp__separator` - Visual separator between groups
#### State Classes
* `.input-otp__slot[data-active="true"]` - Currently active slot
* `.input-otp__slot[data-filled="true"]` - Slot with a character
* `.input-otp__slot[data-disabled="true"]` - Disabled slot
* `.input-otp__slot[data-invalid="true"]` - Invalid slot
* `.input-otp__container[data-disabled="true"]` - Disabled container
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
* **Hover**: `:hover` or `[data-hovered="true"]` on slot
* **Active**: `[data-active="true"]` on slot (currently focused)
* **Filled**: `[data-filled="true"]` on slot (contains a character)
* **Disabled**: `[data-disabled="true"]` on container and slots
* **Invalid**: `[data-invalid="true"]` on slots
## API Reference
### InputOTP Props
InputOTP is built on top of the [input-otp](https://github.com/guilhermerodz/input-otp) library with additional features.
#### Base Props
| Prop | Type | Default | Description |
| -------------------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `maxLength` | `number` | - | **Required.** Number of input slots. |
| `value` | `string` | - | Controlled value (uncontrolled if not provided). |
| `onChange` | `(value: string) => void` | - | Handler called when the value changes. |
| `onComplete` | `(value: string) => void` | - | Handler called when all slots are filled. |
| `className` | `string` | - | Additional CSS classes for the container. |
| `containerClassName` | `string` | - | CSS classes for the inner container. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `children` | `React.ReactNode` | - | InputOTP.Group, InputOTP.Slot, and InputOTP.Separator components. |
#### Validation Props
| Prop | Type | Default | Description |
| ------------------- | --------------- | ------- | ----------------------------------------- |
| `isDisabled` | `boolean` | `false` | Whether the input is disabled. |
| `isInvalid` | `boolean` | `false` | Whether the input is in an invalid state. |
| `validationErrors` | `string[]` | - | Server-side or custom validation errors. |
| `validationDetails` | `ValidityState` | - | HTML5 validation details. |
#### Input Props
| Prop | Type | Default | Description |
| ------------------ | --------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------ |
| `pattern` | `string` | - | Regex pattern for allowed characters (e.g., `REGEXP_ONLY_DIGITS`). |
| `textAlign` | `'left' \| 'center' \| 'right'` | `'left'` | Text alignment within slots. |
| `inputMode` | `'numeric' \| 'text' \| 'decimal' \| 'tel' \| 'search' \| 'email' \| 'url'` | `'numeric'` | Virtual keyboard type on mobile devices. |
| `placeholder` | `string` | - | Placeholder text for empty slots. |
| `pasteTransformer` | `(text: string) => string` | - | Transform pasted text (e.g., remove hyphens). |
#### Form Props
| Prop | Type | Default | Description |
| ----------- | --------- | ------- | ----------------------------------------- |
| `name` | `string` | - | Name attribute for form submission. |
| `autoFocus` | `boolean` | - | Whether to focus the first slot on mount. |
### InputOTP.Group Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ------------------------------------- |
| `className` | `string` | - | Additional CSS classes for the group. |
| `children` | `React.ReactNode` | - | InputOTP.Slot components. |
### InputOTP.Slot Props
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ------------------------------------------- |
| `index` | `number` | - | **Required.** Zero-based index of the slot. |
| `className` | `string` | - | Additional CSS classes for the slot. |
### InputOTP.Separator Props
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ----------------------------------------- |
| `className` | `string` | - | Additional CSS classes for the separator. |
### Exported Patterns
DarkCode UI re-exports common regex patterns from input-otp for convenience:
```tsx
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from '@darkcode-ui/react';
// Use with pattern prop
{/* ... */}
```
* **REGEXP\_ONLY\_DIGITS** - Only numeric characters (0-9)
* **REGEXP\_ONLY\_CHARS** - Only alphabetic characters (a-z, A-Z)
* **REGEXP\_ONLY\_DIGITS\_AND\_CHARS** - Alphanumeric characters (0-9, a-z, A-Z)
# Input
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/input
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/input.mdx
> Primitive single-line text input component that accepts standard HTML attributes
## Import
```tsx
import { Input } from '@darkcode-ui/react';
```
For validation, labels, and error messages, see **[TextField](/docs/components/text-field)**.
### Usage
```tsx
import {Input} from "@darkcode-ui/react";
export function Basic() {
return ;
}
```
### Input Types
```tsx
import {Input, Label} from "@darkcode-ui/react";
export function Types() {
return (
);
}
```
### Controlled
```tsx
"use client";
import {Input} from "@darkcode-ui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("ui.darkcode.dev");
return (
setValue(event.target.value)}
/>
https://{value || "your-domain"}
);
}
```
### Full Width
```tsx
import {Input} from "@darkcode-ui/react";
export function FullWidth() {
return (
);
}
```
### Variants
The Input component supports two visual variants:
* **`primary`** (default) - Standard styling with shadow, suitable for most use cases
* **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
import {Input} from "@darkcode-ui/react";
export function Variants() {
return (
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Input, Surface} from "@darkcode-ui/react";
export function OnSurface() {
return (
);
}
```
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **TextArea**: Multiline text input with focus management
* **Label**: Accessible label for form controls
## Styling
### Passing Tailwind CSS classes
```tsx
import {Input, Label} from '@darkcode-ui/react';
function CustomInput() {
return (
Project name
);
}
```
### Customizing the component classes
The base class `.input` powers every instance. Override it once with `@layer components`.
```css
@layer components {
.input {
@apply rounded-lg border border-border bgsurface px-4 py-2 text-sm shadow-sm transition-colors;
&:hover,
&[data-hovered="true"] {
@apply bg-surface-secondary border-border/80;
}
&:focus-visible,
&[data-focus-visible="true"] {
@apply border-primary ring-2 ring-primary/20;
}
&[data-invalid="true"] {
@apply border-danger bg-danger-50/10 text-danger;
}
}
}
```
### CSS Classes
* `.input` – Native input element styling
### Interactive States
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Focus Visible**: `:focus-visible` or `[data-focus-visible="true"]`
* **Invalid**: `[data-invalid="true"]` (also syncs with `aria-invalid`)
* **Disabled**: `:disabled` or `[aria-disabled="true"]`
* **Read Only**: `[aria-readonly="true"]`
## API Reference
### Input Props
Input accepts all standard HTML ` ` attributes plus the following:
| Prop | Type | Default | Description |
| -------------- | ------------------------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `className` | `string` | - | Tailwind classes merged with the component styles. |
| `type` | `string` | `"text"` | Input type (text, email, password, number, etc.). |
| `value` | `string` | - | Controlled value. |
| `defaultValue` | `string` | - | Uncontrolled initial value. |
| `onChange` | `(event: React.ChangeEvent) => void` | - | Change handler. |
| `placeholder` | `string` | - | Placeholder text. |
| `disabled` | `boolean` | `false` | Disables the input. |
| `readOnly` | `boolean` | `false` | Makes the input read-only. |
| `required` | `boolean` | `false` | Marks the input as required. |
| `name` | `string` | - | Name for form submission. |
| `autoComplete` | `string` | - | Autocomplete hint for the browser. |
| `maxLength` | `number` | - | Maximum number of characters. |
| `minLength` | `number` | - | Minimum number of characters. |
| `pattern` | `string` | - | Regex pattern for validation. |
| `min` | `number \| string` | - | Minimum value (for number/date inputs). |
| `max` | `number \| string` | - | Maximum value (for number/date inputs). |
| `step` | `number \| string` | - | Stepping interval (for number inputs). |
| `fullWidth` | `boolean` | `false` | Whether the input should take full width of its container |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
> For validation props like `isInvalid`, `isRequired`, and error handling, use **[TextField](/docs/components/text-field)** with Input as a child component.
# Label
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/label
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/label.mdx
> Renders an accessible label associated with form controls
## Import
```tsx
import { Label } from '@darkcode-ui/react';
```
## Usage
```tsx
import {Input, Label} from "@darkcode-ui/react";
export function Basic() {
return (
Name
);
}
```
## Related Components
* **Input**: Single-line text input built on React Aria
* **TextArea**: Multiline text input with focus management
* **Fieldset**: Group related form controls with legends
## API
### Label Props
| Prop | Type | Default | Description |
| ------------ | ----------- | ------- | -------------------------------------------------- |
| `htmlFor` | `string` | - | The id of the element the label is associated with |
| `isRequired` | `boolean` | `false` | Whether to display a required indicator |
| `isDisabled` | `boolean` | `false` | Whether the label is in a disabled state |
| `isInvalid` | `boolean` | `false` | Whether the label is in an invalid state |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The content of the label |
## Accessibility
The Label component is built on the native HTML `` element ([MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)) and follows WAI-ARIA best practices:
* Associates with form controls using the `htmlFor` attribute
* Provides semantic HTML `` element
* Supports keyboard navigation when associated with form controls
* Communicates required and invalid states to screen readers
* Clicking the label focuses/activates the associated form control
## Related Components
* **Input**: Single-line text input built on React Aria
* **TextArea**: Multiline text input with focus management
* **Fieldset**: Group related form controls with legends
## Styling
### CSS Classes
The Label component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/label.css)):
#### Base Classes
* `.label` - Base label styles with text styling
#### State Modifier Classes
* `.label--required` or `[data-required="true"] > .label` - Shows required asterisk indicator
* `.label--disabled` or `[data-disabled="true"] .label` - Disabled state styling
* `.label--invalid` or `[data-invalid="true"] .label` or `[aria-invalid="true"] .label` - Invalid state styling (danger/red text color)
**Note**: The required asterisk is smartly applied using role and data-slot detection. It excludes:
* Elements with `role="group"`, `role="radiogroup"`, or `role="checkboxgroup"`
* Elements with `data-slot="radio"` or `data-slot="checkbox"`
This prevents duplicate asterisks when using group components with required fields.
## Examples
### With Required Indicator
```tsx
Email Address
```
### With Disabled State
```tsx
Username
```
### With Invalid State
```tsx
Password
```
# NumberField
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/number-field
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/number-field.mdx
> Number input fields with increment/decrement buttons, validation, and internationalized formatting
## Import
```tsx
import { NumberField } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Label, NumberField} from "@darkcode-ui/react";
export function Basic() {
return (
Width
);
}
```
### Anatomy
```tsx
import {NumberField, Label, Description, FieldError} from '@darkcode-ui/react';
export default () => (
)
```
> **NumberField** allows users to enter numeric values with optional increment/decrement buttons. It supports internationalized formatting, validation, and keyboard navigation.
### With Description
```tsx
import {Description, Label, NumberField} from "@darkcode-ui/react";
export function WithDescription() {
return (
Width
Enter the width in pixels
Percentage
Value must be between 0 and 100
);
}
```
### Required Field
```tsx
import {Description, Label, NumberField} from "@darkcode-ui/react";
export function Required() {
return (
Quantity
Rating
Rate from 1 to 10
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
import {FieldError, Label, NumberField} from "@darkcode-ui/react";
export function Validation() {
return (
Quantity
Quantity must be greater than or equal to 0
Percentage
Percentage must be between 0 and 100
);
}
```
### Controlled
Control the value to synchronize with other components or perform custom formatting.
```tsx
"use client";
import {Button, Description, Label, NumberField} from "@darkcode-ui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState(1024);
return (
Width
Current value: {value}
setValue(0)}>
Reset to 0
setValue(2048)}>
Set to 2048
);
}
```
### With Validation
Implement custom validation logic with controlled values.
```tsx
"use client";
import {Description, FieldError, Label, NumberField} from "@darkcode-ui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState(undefined);
const isInvalid = value !== undefined && (value < 0 || value > 100);
return (
Percentage
{isInvalid ? (
Percentage must be between 0 and 100
) : (
Enter a value between 0 and 100
)}
);
}
```
### Step Values
Configure increment/decrement step values for precise control.
```tsx
import {Description, Label, NumberField} from "@darkcode-ui/react";
export function WithStep() {
return (
Step: 1
Increments by 1
Step: 5
Increments by 5
Step: 10
Increments by 10
);
}
```
### Format Options
Format numbers as currency, percentages, decimals, or units with internationalization support.
```tsx
import {Description, Label, NumberField} from "@darkcode-ui/react";
export function WithFormatOptions() {
return (
Currency (EUR - Accounting)
Accounting format with EUR currency
Currency (USD)
Standard USD currency format
Percentage
Percentage format (0-1, where 0.5 = 50%)
Decimal (2 decimal places)
Decimal format with 2 decimal places
Unit (Kilograms)
Unit format with kilograms
);
}
```
### Custom Icons
Customize the increment and decrement button icons.
```tsx
import {Description, Label, NumberField} from "@darkcode-ui/react";
export function CustomIcons() {
return (
Width (Custom Icons)
Custom icon children
);
}
```
### With Chevrons
Use chevron icons in a vertical layout for a different visual style.
```tsx
import {Label, NumberField} from "@darkcode-ui/react";
export function WithChevrons() {
return (
Number field with chevrons
);
}
```
### Disabled State
```tsx
import {Description, Label, NumberField} from "@darkcode-ui/react";
export function Disabled() {
return (
Width
Enter the width in pixels
Percentage
Value must be between 0 and 100
);
}
```
### Full Width
```tsx
import {Label, NumberField} from "@darkcode-ui/react";
export function FullWidth() {
return (
Width
);
}
```
### Variants
The NumberField component supports two visual variants:
* **`primary`** (default) - Standard styling with shadow, suitable for most use cases
* **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
import {Label, NumberField} from "@darkcode-ui/react";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Description, Label, NumberField, Surface} from "@darkcode-ui/react";
export function OnSurface() {
return (
Width
Enter the width in pixels
Percentage
Value must be between 0 and 100
);
}
```
### Form Example
Complete form integration with validation and submission handling.
```tsx
"use client";
import {
Button,
Description,
FieldError,
Form,
Label,
NumberField,
Spinner,
} from "@darkcode-ui/react";
import React from "react";
export function FormExample() {
const [value, setValue] = React.useState(undefined);
const [isSubmitting, setIsSubmitting] = React.useState(false);
const STOCK_AVAILABLE = 3;
const isOutOfStock = value !== undefined && value > STOCK_AVAILABLE;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (value === undefined || value === null || value < 1 || value > STOCK_AVAILABLE) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("Order submitted:", {quantity: value});
setValue(undefined);
setIsSubmitting(false);
}, 1500);
};
return (
Order quantity
{isOutOfStock ? (
Only {STOCK_AVAILABLE} items left in stock
) : (
Only {STOCK_AVAILABLE} items available
)}
STOCK_AVAILABLE}
isPending={isSubmitting}
type="submit"
variant="primary"
>
{isSubmitting ? (
<>
Processing...
>
) : (
"Place Order"
)}
);
}
```
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **FieldError**: Inline validation messages for form fields
### Custom Render Function
```tsx
"use client";
import {Label, NumberField} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}
>
Width
);
}
```
## Number Stepper
The stepper pattern swaps the editable input for an animated value display — `NumberField.Value`, powered by [Number Flow](https://number-flow.barvian.me/). Compose `DecrementButton` → `Value` → `IncrementButton` inside the group. The group automatically renders a visually-hidden input, so the field stays a focusable, screen-reader-announced spinbutton while the value animates as it changes.
```tsx
import {NumberField} from '@darkcode-ui/react';
export default () => (
);
```
```tsx
import {NumberField} from "@darkcode-ui/react";
export function Stepper() {
return (
);
}
```
### Guest Picker
```tsx
import {NumberField} from "@darkcode-ui/react";
export function StepperGuestPicker() {
return (
);
}
```
### Stepper — Controlled
```tsx
"use client";
import {Button, NumberField} from "@darkcode-ui/react";
import React from "react";
export function StepperControlled() {
const [value, setValue] = React.useState(3);
return (
setValue(0)}>
Reset
setValue(10)}>
Set to 10
Current value: {value}
);
}
```
### Stepper — Custom Icons
```tsx
import {NumberField} from "@darkcode-ui/react";
export function StepperCustomIcons() {
return (
);
}
```
### Custom Value
Pass a render function to `NumberField.Value` to fully customize the rendered content.
```tsx
"use client";
import {NumberField} from "@darkcode-ui/react";
export function StepperCustomValue() {
return (
{({value}) => (
{value} {value === 1 ? "serving" : "servings"}
)}
);
}
```
### Stepper — Disabled
```tsx
import {NumberField} from "@darkcode-ui/react";
export function StepperDisabled() {
return (
);
}
```
### Min / Max Values
The increment and decrement buttons disable automatically at the bounds.
```tsx
import {NumberField} from "@darkcode-ui/react";
export function StepperMinMax() {
return (
Min 0, Max 5 — buttons disable at the bounds
);
}
```
### Reversed Layout
```tsx
import {NumberField} from "@darkcode-ui/react";
export function StepperReversedLayout() {
return (
);
}
```
### Sizes
Use the `size` prop (`sm`, `md`, `lg`) on the root.
```tsx
import {NumberField} from "@darkcode-ui/react";
const sizes = ["sm", "md", "lg"] as const;
export function StepperSizes() {
return (
{sizes.map((size) => (
))}
);
}
```
### With Custom Buttons
```tsx
import {NumberField} from "@darkcode-ui/react";
export function StepperCustomButtons() {
return (
);
}
```
### Stepper — Format Options
```tsx
import {NumberField} from "@darkcode-ui/react";
export function StepperFormatOptions() {
return (
);
}
```
### With Label
```tsx
import {Description, Label, NumberField} from "@darkcode-ui/react";
export function StepperWithLabel() {
return (
Tickets
How many tickets would you like?
);
}
```
### Stepper — With Step
```tsx
import {Label, NumberField} from "@darkcode-ui/react";
export function StepperWithStep() {
return (
Step: 5
Step: 10
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {NumberField, Label} from '@darkcode-ui/react';
function CustomNumberField() {
return (
Quantity
);
}
```
### Customizing the component classes
NumberField uses CSS classes that can be customized. Override the component classes to match your design system.
```css
@layer components {
.number-field {
@apply flex flex-col gap-1;
}
/* When invalid, the description is hidden automatically */
.number-field[data-invalid="true"] [data-slot="description"],
.number-field[aria-invalid="true"] [data-slot="description"] {
@apply hidden;
}
.number-field__group {
@apply bg-field text-field-foreground shadow-field rounded-field inline-flex h-9 items-center overflow-hidden border;
}
.number-field__input {
@apply flex-1 rounded-none border-0 bg-transparent px-3 py-2 tabular-nums;
}
.number-field__increment-button,
.number-field__decrement-button {
@apply flex h-full w-10 items-center justify-center rounded-none bg-transparent;
}
}
```
### CSS Classes
* `.number-field` – Root container with minimal styling (`flex flex-col gap-1`)
* `.number-field__group` – Container for input and buttons with border and background styling
* `.number-field__input` – The numeric input field
* `.number-field__increment-button` – Button to increment the value
* `.number-field__decrement-button` – Button to decrement the value
* `.number-field--primary` – Primary variant with shadow (default)
* `.number-field--secondary` – Secondary variant without shadow, suitable for use in surfaces
> **Note:** Child components ([Label](/docs/components/label), [Description](/docs/components/description), [FieldError](/docs/components/field-error)) have their own CSS classes and styling. See their respective documentation for customization options.
### Interactive States
NumberField automatically manages these data attributes based on its state:
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` - Automatically hides the description slot when invalid
* **Disabled**: `[data-disabled="true"]` - Applied when `isDisabled` is true
* **Focus Within**: `[data-focus-within="true"]` - Applied when the input or buttons are focused
* **Focus Visible**: `[data-focus-visible="true"]` - Applied when focus is visible (keyboard navigation)
* **Hovered**: `[data-hovered="true"]` - Applied when hovering over buttons
Additional attributes are available through render props (see NumberFieldRenderProps below).
## API Reference
### NumberField Props
NumberField inherits all props from React Aria's [NumberField](https://react-spectrum.adobe.com/react-aria/NumberField.html) component.
#### Base Props
| Prop | Type | Default | Description |
| ----------- | -------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `children` | `React.ReactNode \| (values: NumberFieldRenderProps) => React.ReactNode` | - | Child components (Label, Group, Input, etc.) or render function. |
| `className` | `string \| (values: NumberFieldRenderProps) => string` | - | CSS classes for styling, supports render props. |
| `style` | `React.CSSProperties \| (values: NumberFieldRenderProps) => React.CSSProperties` | - | Inline styles, supports render props. |
| `fullWidth` | `boolean` | `false` | Whether the number field should take full width of its container |
| `id` | `string` | - | The element's unique identifier. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size variant. Affects the group height, the button column width, and the stepper value text size. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
#### Value Props
| Prop | Type | Default | Description |
| -------------- | -------------------------------------- | ------- | -------------------------------------- |
| `value` | `number` | - | Current value (controlled). |
| `defaultValue` | `number` | - | Default value (uncontrolled). |
| `onChange` | `(value: number \| undefined) => void` | - | Handler called when the value changes. |
#### Formatting Props
| Prop | Type | Default | Description |
| --------------- | -------------------------- | ------- | ------------------------------------------------------------------ |
| `formatOptions` | `Intl.NumberFormatOptions` | - | Options for formatting numbers (currency, percent, decimal, unit). |
| `locale` | `string` | - | Locale for number formatting. |
#### Validation Props
| Prop | Type | Default | Description |
| -------------------- | ----------------------------------------------------------------- | ---------- | -------------------------------------------------------------- |
| `isRequired` | `boolean` | `false` | Whether user input is required before form submission. |
| `isInvalid` | `boolean` | - | Whether the value is invalid. |
| `validate` | `(value: number) => ValidationError \| true \| null \| undefined` | - | Custom validation function. |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA attributes. |
| `validationErrors` | `string[]` | - | Server-side validation errors. |
#### Range Props
| Prop | Type | Default | Description |
| ---------- | -------- | ------- | ---------------------------------------------- |
| `minValue` | `number` | - | Minimum allowed value. |
| `maxValue` | `number` | - | Maximum allowed value. |
| `step` | `number` | `1` | Step value for increment/decrement operations. |
#### State Props
| Prop | Type | Default | Description |
| ------------ | --------- | ------- | -------------------------------------------------- |
| `isDisabled` | `boolean` | - | Whether the input is disabled. |
| `isReadOnly` | `boolean` | - | Whether the input can be selected but not changed. |
#### Form Props
| Prop | Type | Default | Description |
| ----------- | --------- | ------- | ---------------------------------------------------- |
| `name` | `string` | - | Name of the input element, for HTML form submission. |
| `autoFocus` | `boolean` | - | Whether the element should receive focus on render. |
#### Accessibility Props
| Prop | Type | Default | Description |
| ------------------ | -------- | ------- | ----------------------------------------------------- |
| `aria-label` | `string` | - | Accessibility label when no visible label is present. |
| `aria-labelledby` | `string` | - | ID of elements that label this field. |
| `aria-describedby` | `string` | - | ID of elements that describe this field. |
| `aria-details` | `string` | - | ID of elements with additional details. |
### Composition Components
NumberField works with these separate components that should be imported and used directly:
* **NumberField.Group** - Container for input and buttons
* **NumberField.Input** - The numeric input field
* **NumberField.Value** - Animated value display (Number Flow) for the stepper pattern
* **NumberField.IncrementButton** - Button to increment the value
* **NumberField.DecrementButton** - Button to decrement the value
* **Label** - Field label component from `@darkcode-ui/react`
* **Description** - Helper text component from `@darkcode-ui/react`
* **FieldError** - Validation error message from `@darkcode-ui/react`
Each of these components has its own props API. Use them directly within NumberField for composition:
```tsx
Quantity
Enter a value between 0 and 100
Value must be between 0 and 100
```
#### NumberField.Group Props
NumberField.Group inherits props from React Aria's [Group](https://react-spectrum.adobe.com/react-aria/Group.html) component.
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------ | ------- | ----------------------------------------------------- |
| `children` | `React.ReactNode \| (values: GroupRenderProps) => React.ReactNode` | - | Child components (Input, Buttons) or render function. |
| `className` | `string \| (values: GroupRenderProps) => string` | - | CSS classes for styling. |
#### NumberField.Input Props
NumberField.Input inherits props from React Aria's [Input](https://react-spectrum.adobe.com/react-aria/Input.html) component.
| Prop | Type | Default | Description |
| ----------- | -------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `className` | `string` | - | CSS classes for styling. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the input. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
#### NumberField.IncrementButton Props
NumberField.IncrementButton inherits props from React Aria's [Button](https://react-spectrum.adobe.com/react-aria/Button.html) component.
| Prop | Type | Default | Description |
| ----------- | ----------------- | -------------- | ------------------------------------------------------ |
| `children` | `React.ReactNode` | ` ` | Icon or content for the button. Defaults to plus icon. |
| `className` | `string` | - | CSS classes for styling. |
| `slot` | `"increment"` | `"increment"` | Must be set to "increment" (automatically set). |
#### NumberField.DecrementButton Props
NumberField.DecrementButton inherits props from React Aria's [Button](https://react-spectrum.adobe.com/react-aria/Button.html) component.
| Prop | Type | Default | Description |
| ----------- | ----------------- | --------------- | ------------------------------------------------------- |
| `children` | `React.ReactNode` | ` ` | Icon or content for the button. Defaults to minus icon. |
| `className` | `string` | - | CSS classes for styling. |
| `slot` | `"decrement"` | `"decrement"` | Must be set to "decrement" (automatically set). |
#### NumberField.Value Props
`NumberField.Value` renders the field's current value as an animated number via [Number Flow](https://number-flow.barvian.me/). It also accepts all Number Flow props except `value` and `children`.
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------- |
| `value` | `number` | field value | Override the displayed value. Defaults to the field's current value. |
| `format` | `Intl.NumberFormatOptions` | root `formatOptions` | Override the format options used to render the number. |
| `children` | `React.ReactNode \| ((args: { value: number; formatOptions?: Intl.NumberFormatOptions }) => React.ReactNode)` | - | Custom content or render function that replaces the animated display. |
| `className` | `string` | - | CSS classes for styling. |
### NumberFieldRenderProps
When using render props with `className`, `style`, or `children`, these values are available:
| Prop | Type | Description |
| ---------------- | --------------------- | -------------------------------------------------------------------------- |
| `isDisabled` | `boolean` | Whether the field is disabled. |
| `isInvalid` | `boolean` | Whether the field is currently invalid. |
| `isReadOnly` | `boolean` | Whether the field is read-only. |
| `isRequired` | `boolean` | Whether the field is required. |
| `isFocused` | `boolean` | Whether the field is currently focused (DEPRECATED - use `isFocusWithin`). |
| `isFocusWithin` | `boolean` | Whether any child element is focused. |
| `isFocusVisible` | `boolean` | Whether focus is visible (keyboard navigation). |
| `value` | `number \| undefined` | Current value. |
| `minValue` | `number \| undefined` | Minimum allowed value. |
| `maxValue` | `number \| undefined` | Maximum allowed value. |
| `step` | `number` | Step value for increment/decrement. |
# RadioButtonGroup
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/radio-button-group
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/radio-button-group.mdx
> A single-selection button group with card-style radio options, icons, and custom indicators
## Import
```tsx
import { RadioButtonGroup } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
const plans = [
{description: "For side projects and small teams", title: "Starter", value: "starter"},
{description: "Advanced reporting and analytics", title: "Pro", value: "pro"},
{description: "Share access with up to 10 teammates", title: "Teams", value: "teams"},
];
export function Default() {
return (
Subscription plan
{plans.map((plan) => (
{plan.title}
{plan.description}
))}
);
}
```
### Anatomy
Import the RadioButtonGroup component and access all parts using dot notation.
```tsx
import {RadioButtonGroup, Label, Description} from '@darkcode-ui/react';
export default () => (
)
```
### Delivery & Payment
### Subscription Plans
```tsx
import {Chip, Label, RadioButtonGroup} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
const subscriptions = [
{features: ["10 projects", "Community support"], price: "$0", title: "Free", value: "free"},
{
badge: "Most popular",
features: ["Unlimited projects", "Priority support", "Advanced analytics"],
price: "$12",
title: "Pro",
value: "pro",
},
{
features: ["Everything in Pro", "SSO & SAML", "Dedicated manager"],
price: "$49",
title: "Enterprise",
value: "enterprise",
},
];
export function SubscriptionPlans() {
return (
Choose a plan
{subscriptions.map((plan) => (
{plan.title}
{plan.badge ? (
{plan.badge}
) : null}
{plan.price}
/month
{plan.features.map((feature) => (
{feature}
))}
))}
);
}
```
### Controlled
```tsx
"use client";
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
import React from "react";
const plans = [
{description: "For side projects and small teams", title: "Starter", value: "starter"},
{description: "Advanced reporting and analytics", title: "Pro", value: "pro"},
{description: "Share access with up to 10 teammates", title: "Teams", value: "teams"},
];
export function Controlled() {
const [value, setValue] = React.useState("pro");
return (
Subscription plan
{plans.map((plan) => (
{plan.title}
{plan.description}
))}
Selected plan: {value}
);
}
```
### Custom Indicator
Pass children to `RadioButtonGroup.Indicator` to render a custom icon that appears only when the item is selected.
```tsx
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
const plans = [
{description: "For side projects and small teams", title: "Starter", value: "starter"},
{description: "Advanced reporting and analytics", title: "Pro", value: "pro"},
{description: "Share access with up to 10 teammates", title: "Teams", value: "teams"},
];
export function CustomIndicator() {
return (
Subscription plan
{plans.map((plan) => (
{plan.title}
{plan.description}
))}
);
}
```
### Disabled Group
```tsx
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
const plans = [
{description: "For side projects and small teams", title: "Starter", value: "starter"},
{description: "Advanced reporting and analytics", title: "Pro", value: "pro"},
{description: "Share access with up to 10 teammates", title: "Teams", value: "teams"},
];
export function DisabledGroup() {
return (
Subscription plan
Plan changes are paused while we roll out updates.
{plans.map((plan) => (
{plan.title}
{plan.description}
))}
);
}
```
### Grid Layout
Set `layout="grid"` to arrange items in a responsive grid. Override the columns with a Tailwind class on the root.
### Icon Cards
### No Indicator
Omit `RadioButtonGroup.Indicator` to rely on the selection ring alone.
```tsx
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
const plans = [
{description: "For side projects and small teams", title: "Starter", value: "starter"},
{description: "Advanced reporting and analytics", title: "Pro", value: "pro"},
{description: "Share access with up to 10 teammates", title: "Teams", value: "teams"},
];
export function NoIndicator() {
return (
Subscription plan
{plans.map((plan) => (
{plan.title}
{plan.description}
))}
);
}
```
### Render Prop Children
`RadioButtonGroup.Item` accepts a render function that receives the radio's selection state.
```tsx
"use client";
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
const plans = [
{description: "For side projects and small teams", title: "Starter", value: "starter"},
{description: "Advanced reporting and analytics", title: "Pro", value: "pro"},
{description: "Share access with up to 10 teammates", title: "Teams", value: "teams"},
];
export function RenderPropChildren() {
return (
Subscription plan
{plans.map((plan) => (
{({isSelected}) => (
<>
{plan.title}
{isSelected ? "Selected plan" : plan.description}
>
)}
))}
);
}
```
### With Icons
```tsx
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
const options = [
{
description: "Best for individuals",
icon: "gravity-ui:person",
title: "Personal",
value: "personal",
},
{description: "Best for small teams", icon: "gravity-ui:persons", title: "Team", value: "team"},
{description: "Advanced controls", icon: "gravity-ui:gear", title: "Custom", value: "custom"},
];
export function WithIcons() {
return (
Workspace
{options.map((option) => (
{option.title}
{option.description}
))}
);
}
```
### With Ripple
Add the `ripple` prop to `RadioButtonGroup.Item` for a press ripple effect.
```tsx
import {Description, Label, RadioButtonGroup} from "@darkcode-ui/react";
const plans = [
{description: "For side projects and small teams", title: "Starter", value: "starter"},
{description: "Advanced reporting and analytics", title: "Pro", value: "pro"},
{description: "Share access with up to 10 teammates", title: "Teams", value: "teams"},
];
export function WithRipple() {
return (
Subscription plan
{plans.map((plan) => (
{plan.title}
{plan.description}
))}
);
}
```
## Related Components
* **RadioGroup**: Single selection from multiple options
* **Fieldset**: Group related form controls with legends
* **Surface**: Base container surface
## Styling
RadioButtonGroup builds on top of the [RadioGroup](/docs/components/radio-group) and Radio
primitives, adding card-style layout, a positioned indicator, and a leading icon slot.
### CSS Classes
The RadioButtonGroup component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/radio-button-group.css)):
#### Base Classes
* `.radio-button-group` - Base RadioGroup container with flex layout
#### Layout Classes
* `.radio-button-group--grid` - Grid layout mode
#### Element Classes
* `.radio-button-group__item` - Card-like radio button with border and selection ring
* `.radio-button-group__indicator` - Positioned top-right indicator (radio dot or custom icon)
* `.radio-button-group__item-content` - Text/content area wrapping Radio.Content
* `.radio-button-group__item-icon` - Leading icon container
### Interactive States
* **Selected**: `[data-selected="true"]` on `.radio-button-group__item` (accent ring)
* **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]` on `.radio-button-group__item` (focus ring)
* **Disabled**: `:disabled` or `[aria-disabled="true"]` on `.radio-button-group__item` (reduced opacity)
### CSS Variables
* `--radio-button-group-item-radius` - Border radius of items (default: `var(--radius-2xl)`)
## API Reference
### RadioButtonGroup
The root component. Wraps DarkCode UI [RadioGroup](/docs/components/radio-group) with card-style layout.
| Prop | Type | Default | Description |
| -------- | ------------------ | -------- | --------------------- |
| `layout` | `'flex' \| 'grid'` | `'flex'` | Layout mode for items |
Also supports all [DarkCode UI RadioGroup](/docs/components/radio-group) props (`value`, `defaultValue`, `onChange`, `isDisabled`, `isRequired`, `name`, `orientation`, `variant`, …).
### RadioButtonGroup.Item
A selectable card wrapping DarkCode UI Radio. Supports render prop children for accessing selection state.
| Prop | Type | Default | Description |
| -------- | --------- | ------- | -------------------------------------------- |
| `ripple` | `boolean` | `false` | Whether a ripple animation is shown on press |
Also supports all DarkCode UI Radio props (`value`, `isDisabled`, `children`, …).
### RadioButtonGroup.Indicator
Selection indicator positioned at the top-right of the item.
* **No children**: renders the default DarkCode UI radio dot (Control + Indicator)
* **With children**: renders a custom icon that appears only when selected
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------------------- |
| `children` | `ReactNode` | - | Custom indicator icon (shown when selected) |
Also supports all native `span` HTML attributes.
### RadioButtonGroup.ItemContent
Content area for title and description text. Wraps DarkCode UI `Radio.Content`.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ---------------- |
| `children` | `ReactNode` | - | Content elements |
Also supports all native `div` HTML attributes.
### RadioButtonGroup.ItemIcon
Leading icon container.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ------------ |
| `children` | `ReactNode` | - | Icon element |
Also supports all native `div` HTML attributes.
# RadioGroup
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/radio-group
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/radio-group.mdx
> Radio group for selecting a single option from a list
## Import
```tsx
import { RadioGroup, Radio } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
export function Basic() {
return (
Plan selection
Choose the plan that suits you best
Basic Plan
Includes 100 messages per month
Premium Plan
Includes 200 messages per month
Business Plan
Unlimited messages
);
}
```
### Anatomy
Import the RadioGroup component and access all parts using dot notation.
```tsx
import {RadioGroup, Radio, Label, Description, FieldError} from '@darkcode-ui/react';
export default () => (
✓ {/* Custom indicator (optional) */}
Label
{/* Optional, sibling of Radio.Content */}
)
```
### Custom Indicator
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
export function CustomIndicator() {
return (
Plan selection
Choose the plan that suits you best
{({isSelected}) =>
isSelected ? ✓ : null
}
Basic Plan
Includes 100 messages per month
{({isSelected}) =>
isSelected ? ✓ : null
}
Premium Plan
Includes 200 messages per month
{({isSelected}) =>
isSelected ? ✓ : null
}
Business Plan
Unlimited messages
);
}
```
### Horizontal Orientation
```tsx
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
export function Horizontal() {
return (
Subscription plan
Starter
For side projects
Pro
Advanced reporting
Teams
Up to 10 teammates
);
}
```
### Controlled
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("pro");
return (
Subscription plan
Starter
For side projects and small teams
Pro
Advanced reporting and analytics
Teams
Share access with up to 10 teammates
Selected plan: {value}
);
}
```
### Uncontrolled
Combine `defaultValue` with `onChange` when you only need to react to updates.
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
import React from "react";
export function Uncontrolled() {
const [selection, setSelection] = React.useState("pro");
return (
setSelection(nextValue)}
>
Subscription plan
Starter
For side projects and small teams
Pro
Advanced reporting and analytics
Teams
Share access with up to 10 teammates
Last chosen plan: {selection}
);
}
```
### Validation
```tsx
"use client";
import {Button, Description, FieldError, Form, Label, Radio, RadioGroup} from "@darkcode-ui/react";
import React from "react";
export function Validation() {
const [message, setMessage] = React.useState(null);
return (
{
e.preventDefault();
const formData = new FormData(e.currentTarget);
const value = formData.get("plan-validation");
setMessage(`Your chosen plan is: ${value}`);
}}
>
Subscription plan
Starter
For side projects and small teams
Pro
Advanced reporting and analytics
Teams
Share access with up to 10 teammates
Choose a subscription before continuing.
Submit
{!!message && {message}
}
);
}
```
### Disabled
```tsx
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
export function Disabled() {
return (
Subscription plan
Plan changes are temporarily paused while we roll out updates.
Starter
For side projects and small teams
Pro
Advanced reporting and analytics
Teams
Share access with up to 10 teammates
);
}
```
### Variants
The RadioGroup component supports two visual variants:
* **`primary`** (default) - Standard styling with default background, suitable for most use cases
* **`secondary`** - Lower emphasis variant, suitable for use in Surface components
```tsx
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
export function Variants() {
return (
Primary variant
Option 1
Standard styling with default background
Option 2
Another option with primary styling
Secondary variant
Option 1
Lower emphasis variant for use in surfaces
Option 2
Another option with secondary styling
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Description, Label, Radio, RadioGroup, Surface} from "@darkcode-ui/react";
export function OnSurface() {
return (
Plan selection
Choose the plan that suits you best
Basic Plan
Includes 100 messages per month
Premium Plan
Includes 200 messages per month
Business Plan
Unlimited messages
);
}
```
### Delivery & Payment
## Related Components
* **RadioButtonGroup**: Card-style single-selection button group
* **Fieldset**: Group related form controls with legends
* **Surface**: Base container surface
### Custom Render Function
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}
>
Plan selection
Choose the plan that suits you best
Basic Plan
Includes 100 messages per month
Premium Plan
Includes 200 messages per month
Business Plan
Unlimited messages
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import { RadioGroup, Radio } from '@darkcode-ui/react';
export default () => (
Basic Plan
Premium Plan
Business Plan
);
```
### Customizing the component classes
To customize the RadioGroup component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.radio-group {
@apply gap-2;
}
.radio {
@apply gap-4 rounded-lg border border-border p-3 hover:bg-surface-hovered;
}
.radio__control {
@apply border-2 border-primary;
}
.radio__indicator {
@apply bg-primary;
}
.radio__content {
@apply gap-1;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The RadioGroup component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/radio-group.css)):
#### Base Classes
* `.radio-group` - Base radio group container
* `.radio` - Individual radio item
* `.radio__control` - Radio control (circular button)
* `.radio__indicator` - Radio indicator (inner dot)
* `.radio__content` - Radio content wrapper
#### Modifier Classes
* `.radio--disabled` - Disabled radio state
### Interactive States
The radio supports both CSS pseudo-classes and data attributes for flexibility:
* **Selected**: `[aria-checked="true"]` or `[data-selected="true"]` (indicator appears)
* **Hover**: `:hover` or `[data-hovered="true"]` (border color changes)
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring)
* **Pressed**: `:active` or `[data-pressed="true"]` (scale transform)
* **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events)
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` (error border color)
## API Reference
### RadioGroup Props
| Prop | Type | Default | Description |
| -------------- | ----------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `value` | `string` | - | The current value (controlled) |
| `defaultValue` | `string` | - | The default value (uncontrolled) |
| `onChange` | `(value: string) => void` | - | Handler called when the value changes |
| `isDisabled` | `boolean` | `false` | Whether the radio group is disabled |
| `isRequired` | `boolean` | `false` | Whether the radio group is required |
| `isReadOnly` | `boolean` | `false` | Whether the radio group is read only |
| `isInvalid` | `boolean` | `false` | Whether the radio group is in an invalid state |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `name` | `string` | - | The name of the radio group, used when submitting an HTML form |
| `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | The orientation of the radio group |
| `children` | `React.ReactNode \| (values: RadioGroupRenderProps) => React.ReactNode` | - | Radio group content or render prop |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Radio Props
| Prop | Type | Default | Description |
| ------------ | ------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------- |
| `value` | `string` | - | The value of the radio button |
| `isDisabled` | `boolean` | `false` | Whether the radio button is disabled |
| `name` | `string` | - | The name of the radio button, used when submitting an HTML form |
| `children` | `React.ReactNode \| (values: RadioRenderProps) => React.ReactNode` | - | Radio content or render prop |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Radio.Control Props
Extends `React.HTMLAttributes`.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ---------------------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | The content to render inside the control wrapper (typically Radio.Indicator) |
### Radio.Indicator Props
Extends `React.HTMLAttributes`.
| Prop | Type | Default | Description |
| ---------- | ------------------------------------------------------------------ | ------- | ---------------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: RadioRenderProps) => React.ReactNode` | - | Optional content or render prop that receives the current radio state. |
### Radio.Content Props
Extends `React.HTMLAttributes`.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ---------------------------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | The content to render inside the content wrapper (typically Label and Description) |
### RadioRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ---------------- | --------- | ---------------------------------------- |
| `isSelected` | `boolean` | Whether the radio is currently selected |
| `isHovered` | `boolean` | Whether the radio is hovered |
| `isPressed` | `boolean` | Whether the radio is currently pressed |
| `isFocused` | `boolean` | Whether the radio is focused |
| `isFocusVisible` | `boolean` | Whether the radio is keyboard focused |
| `isDisabled` | `boolean` | Whether the radio is disabled |
| `isReadOnly` | `boolean` | Whether the radio is read only |
| `isInvalid` | `boolean` | Whether the radio is in an invalid state |
# Rich Text Editor
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/rich-text-editor
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/rich-text-editor.mdx
> A composable Lexical editor with toolbar controls, selection and empty-line menus, slash commands, link popover, JSON value updates, and character count.
## Import
```tsx
import { RichTextEditor } from '@darkcode-ui/react';
```
DarkCode UI builds the Rich Text Editor on **[Lexical](https://lexical.dev)**. The value model is
JSON-first: `value`/`defaultValue` are Lexical `SerializedEditorState` documents, and
`onValueChange` also reports HTML, plain text, empty state, and character/word counts.
## Anatomy
Compose the editor from its parts with dot notation. The root owns the Lexical editor instance and
shares it with every part through context.
```tsx
```
### Usage
```tsx
"use client";
import {RichTextEditor} from "@darkcode-ui/react";
import {EditorBubbleMenu, EditorToolbar} from "./toolbar";
export function Default() {
return (
);
}
```
### Controlled
Pass `value` and `onValueChange` to control the document. The handler receives the JSON document
plus a details object (`{ json, html, text, isEmpty, characterCount, wordCount }`).
```tsx
"use client";
import type {RichTextEditorChangeDetails, RichTextEditorValue} from "@darkcode-ui/react";
import {RichTextEditor} from "@darkcode-ui/react";
import {useState} from "react";
import {EditorBubbleMenu, EditorToolbar} from "./toolbar";
export function Controlled() {
const [value, setValue] = useState(undefined);
const [details, setDetails] = useState(null);
return (
{
setValue(next);
setDetails(nextDetails);
}}
>
{details?.characterCount ?? 0} {" "}
characters · {details?.wordCount ?? 0} {" "}
words ·{" "}
{String(details?.isEmpty ?? true)} {" "}
empty
);
}
```
### Character Count
Add `maxLength` and render `RichTextEditor.CharacterCount` in the footer. It exposes a
`data-over-limit` attribute when the limit is exceeded.
```tsx
"use client";
import {RichTextEditor} from "@darkcode-ui/react";
import {EditorBubbleMenu, EditorToolbar} from "./toolbar";
export function CharacterCount() {
return (
Markdown-style formatting supported
);
}
```
### Placeholder
```tsx
"use client";
import {RichTextEditor} from "@darkcode-ui/react";
import {EditorToolbar} from "./toolbar";
export function Placeholder() {
return (
);
}
```
### Disabled And Read Only
`isDisabled` dims the editor and blocks interaction; `isReadOnly` keeps the content focusable but
not editable.
```tsx
"use client";
import {RichTextEditor} from "@darkcode-ui/react";
import {$createQuoteNode} from "@lexical/rich-text";
import {$createParagraphNode, $createTextNode, $getRoot} from "lexical";
import {EditorToolbar} from "./toolbar";
/** Seeds the editor with a short document. Runs inside `editor.update`. */
function seedDocument() {
const root = $getRoot();
if (root.getFirstChild() !== null) {
return;
}
const intro = $createParagraphNode();
const strong = $createTextNode("DarkCode UI");
strong.toggleFormat("bold");
intro.append(
$createTextNode("The "),
strong,
$createTextNode(" rich text editor keeps its content visible while editing is turned off."),
);
const quote = $createQuoteNode();
quote.append($createTextNode("Write once, render anywhere."));
root.append(intro, quote);
}
export function Disabled() {
return (
);
}
```
### Custom Composition
Rearrange the parts freely — drop the toolbar, keep only the bubble menu, add a footer, and so on.
```tsx
"use client";
import {RichTextEditor} from "@darkcode-ui/react";
export function CustomComposition() {
return (
{({words}) => {words} words }
);
}
```
### Extensible Commands
Use `CommandButton`, `FloatingMenu`, `SuggestionMenu`, and the editor hooks to wire custom Lexical
commands without waiting for a new built-in prop.
```tsx
"use client";
import {
IconCalendar,
IconCode,
IconCodeBlock,
IconHeading1,
IconHeading2,
IconHeading3,
IconListUnordered,
IconQuote,
RichTextEditor,
filterRichTextEditorSuggestionItems,
} from "@darkcode-ui/react";
import {$createCodeNode} from "@lexical/code";
import {INSERT_UNORDERED_LIST_COMMAND} from "@lexical/list";
import {$createHeadingNode, $createQuoteNode} from "@lexical/rich-text";
import {$setBlocksType} from "@lexical/selection";
import {$getSelection, $isRangeSelection} from "lexical";
const slashItems = ({query}: {query: string}) =>
filterRichTextEditorSuggestionItems(
[
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode("h1"));
}
}),
description: "Big section heading",
icon: ,
keywords: ["title", "h1"],
title: "Heading 1",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode("h2"));
}
}),
description: "Medium section heading",
icon: ,
keywords: ["subtitle", "h2"],
title: "Heading 2",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode("h3"));
}
}),
description: "Small section heading",
icon: ,
keywords: ["h3"],
title: "Heading 3",
},
{
command: ({editor}) => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
description: "Create a simple list",
icon: ,
keywords: ["unordered", "ul"],
title: "Bulleted list",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createQuoteNode());
}
}),
description: "Capture a quote",
icon: ,
keywords: ["blockquote"],
title: "Quote",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createCodeNode());
}
}),
description: "Insert a code block",
icon: ,
keywords: ["pre", "snippet"],
title: "Code block",
},
],
query,
);
export function Extensible() {
return (
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
selection.insertText(
new Date().toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric",
}),
);
}
})
}
>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode("h2"));
}
})
}
>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createCodeNode());
}
})
}
>
);
}
```
```tsx
import { RichTextEditor, filterRichTextEditorSuggestionItems } from '@darkcode-ui/react';
import { $createHeadingNode } from '@lexical/rich-text';
import { $setBlocksType } from '@lexical/selection';
import { $getSelection, $isRangeSelection } from 'lexical';
const slashItems = ({ query }) =>
filterRichTextEditorSuggestionItems(
[
{
title: 'Heading 1',
keywords: ['title', 'h1'],
command: ({ editor }) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode('h1'));
}
}),
},
],
query,
);
editor.update(() => {
$getSelection()?.insertText('27 May 2026');
})
}
/>
;
```
Because DarkCode UI uses Lexical (not Tiptap), suggestion-item commands receive `{ editor }` and
the trigger query text is removed for you before the command runs.
### Notion-style block editing
Drop in `RichTextEditor.DraggableBlocks` for a Notion-like gutter — hover a line to reveal a drag
handle (reorder whole blocks) and a "+" button (opens the slash menu). The editor also ships
checklist items (the `checkList` command), a callout block, and a horizontal-rule divider, so a
slash menu can offer the full block palette.
```tsx
"use client";
import {
$createCalloutNode,
INSERT_CHECK_LIST_COMMAND,
INSERT_HORIZONTAL_RULE_COMMAND,
IconCallout,
IconChecklist,
IconCodeBlock,
IconDivider,
IconHeading1,
IconHeading2,
IconListUnordered,
IconQuote,
RichTextEditor,
filterRichTextEditorSuggestionItems,
} from "@darkcode-ui/react";
import {$createCodeNode} from "@lexical/code";
import {INSERT_UNORDERED_LIST_COMMAND} from "@lexical/list";
import {$createHeadingNode, $createQuoteNode} from "@lexical/rich-text";
import {$setBlocksType} from "@lexical/selection";
import {$getSelection, $isRangeSelection} from "lexical";
const slashItems = ({query}: {query: string}) =>
filterRichTextEditorSuggestionItems(
[
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode("h1"));
}
}),
description: "Big section heading",
icon: ,
keywords: ["title", "h1"],
title: "Heading 1",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode("h2"));
}
}),
description: "Medium section heading",
icon: ,
keywords: ["subtitle", "h2"],
title: "Heading 2",
},
{
command: ({editor}) => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
description: "Create a simple list",
icon: ,
keywords: ["unordered", "ul"],
title: "Bulleted list",
},
{
command: ({editor}) => editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),
description: "Track tasks with a to-do list",
icon: ,
keywords: ["todo", "task", "check"],
title: "Checklist",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createQuoteNode());
}
}),
description: "Capture a quote",
icon: ,
keywords: ["blockquote"],
title: "Quote",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createCalloutNode());
}
}),
description: "Make text stand out",
icon: ,
keywords: ["note", "info", "highlight"],
title: "Callout",
},
{
command: ({editor}) => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined),
description: "Visually separate blocks",
icon: ,
keywords: ["hr", "rule", "line"],
title: "Divider",
},
{
command: ({editor}) =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createCodeNode());
}
}),
description: "Insert a code block",
icon: ,
keywords: ["pre", "snippet"],
title: "Code block",
},
],
query,
);
export function Notion() {
return (
);
}
```
## CSS Classes
### Base Classes
* `.rich-text-editor` – Root wrapper
* `.rich-text-editor__shell` – Editor surface
### Element Classes
* `.rich-text-editor__toolbar` – Toolbar container
* `.rich-text-editor__toolbar-group` – Toolbar group
* `.rich-text-editor__toolbar-button` – Toolbar button slot
* `.rich-text-editor__toolbar-separator` – Toolbar separator
* `.rich-text-editor__content` – Lexical content host
* `.rich-text-editor__prosemirror` – Editable element
* `.rich-text-editor__bubble-menu` – Floating selection toolbar
* `.rich-text-editor__bubble-menu-toolbar` – Toolbar inside the floating selection menu
* `.rich-text-editor__floating-menu` – Floating empty-line toolbar
* `.rich-text-editor__floating-menu-toolbar` – Toolbar inside the floating empty-line menu
* `.rich-text-editor__suggestion-menu` – Triggered suggestion menu
* `.rich-text-editor__suggestion-menu-item` – Suggestion menu item
* `.rich-text-editor__footer` – Footer row
* `.rich-text-editor__character-count` – Character and word count
* `.rich-text-editor__link-popover` – Link popover surface
* `.rich-text-editor__link-input` – Link URL input
* `.rich-text-editor__block-menu` – Block gutter (drag handle + add button)
* `.rich-text-editor__drag-handle` – Drag-to-reorder handle
* `.rich-text-editor__add-block` – "+" add-block button
* `.rich-text-editor__drag-target-line` – Drop indicator line
* `.rich-text-editor__callout` – Callout block
* `.rich-text-editor__divider` – Horizontal-rule divider
* `.rich-text-editor__listitem-checked` / `.rich-text-editor__listitem-unchecked` – Checklist items
### States
* **Disabled**: `[data-disabled="true"]` on `.rich-text-editor`
* **Read only**: `[data-readonly="true"]` on `.rich-text-editor`
* **Over limit**: `[data-over-limit="true"]` on `.rich-text-editor__character-count`
* **Active command**: `[data-active="true"]` on toolbar buttons
## API Reference
### RichTextEditor
The root provider. It owns the Lexical editor instance and emits JSON-first value updates.
| Prop | Type | Default | Description |
| --------------- | ---------------------------- | -------------------- | ------------------------------------------------------------------------------ |
| `value` | `SerializedEditorState` | - | Controlled Lexical JSON document |
| `defaultValue` | `SerializedEditorState` | - | Initial uncontrolled JSON document |
| `onValueChange` | `(value, details) => void` | - | Called with JSON plus HTML, text, empty state, character count, and word count |
| `placeholder` | `string` | `"Start writing..."` | Placeholder text for empty content |
| `maxLength` | `number` | - | CharacterCount limit |
| `isDisabled` | `boolean` | `false` | Disables editing and controls |
| `isReadOnly` | `boolean` | `false` | Keeps content focusable but not editable |
| `extensions` | `Klass[]` | - | Additional Lexical nodes appended after the defaults |
| `editorOptions` | `Partial` | - | Advanced Lexical composer options merged after defaults |
The `details` object passed to `onValueChange` is
`{ json, html, text, isEmpty, characterCount, wordCount }`.
### RichTextEditor.ToggleButton
Runs a formatting command and subscribes to active/disabled editor state.
| Prop | Type | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `command` | `"bold" \| "italic" \| "underline" \| "strike" \| "code" \| "blockquote" \| "bulletList" \| "orderedList" \| "checkList" \| "codeBlock" \| "heading-1" \| "heading-2" \| "heading-3"` | Formatting command |
| `tooltip` | `ReactNode` | Optional tooltip content |
### RichTextEditor.ActionButton
Runs editor actions.
| Prop | Type | Description |
| --------- | --------------------------------------------------------- | ------------------------ |
| `action` | `"undo" \| "redo" \| "clearFormatting" \| "clearContent"` | Editor action |
| `tooltip` | `ReactNode` | Optional tooltip content |
### RichTextEditor.CommandButton
Runs any custom Lexical command.
| Prop | Type | Description |
| ------------ | ---------------------------------- | ------------------------------------------------ |
| `onCommand` | `(editor) => void \| boolean` | Command to run with the current editor |
| `isActive` | `boolean \| ((editor) => boolean)` | Active state used for styling and `aria-pressed` |
| `isDisabled` | `boolean \| ((editor) => boolean)` | Command-specific disabled state |
| `tooltip` | `ReactNode` | Optional tooltip content |
### RichTextEditor.LinkPopover
Compound popover for applying and removing links through Lexical's link commands. Subcomponents:
`Trigger`, `Content`, `Input`, `Actions`, `ApplyButton`, and `UnsetButton`.
### RichTextEditor.Content
Renders the Lexical `ContentEditable` and placeholder for the current editor instance.
### RichTextEditor.BubbleMenu
A floating selection toolbar. By default it appears for non-empty text selections.
| Prop | Type | Default | Description |
| -------------- | -------------- | ------- | ------------------------------------------------- |
| `toolbarProps` | `ToolbarProps` | - | Props forwarded to the internal selection toolbar |
### RichTextEditor.FloatingMenu
A floating empty-line toolbar. By default it appears on an empty editable block while the editor is
focused.
| Prop | Type | Default | Description |
| -------------- | -------------- | ------- | ------------------------------------------------- |
| `toolbarProps` | `ToolbarProps` | - | Props forwarded to the internal insertion toolbar |
### RichTextEditor.DraggableBlocks
A Notion-style block gutter. Mount it inside `Shell`; it adds a hover drag handle (reorder whole
blocks) and an optional "+" add-block button.
| Prop | Type | Default | Description |
| ---------------- | --------- | ------- | ----------------------------------------------------------- |
| `showAddButton` | `boolean` | `true` | Show the "+" button that opens the slash menu |
| `addTriggerChar` | `string` | `"/"` | Character inserted by the add button (empty string to skip) |
### RichTextEditor.SuggestionMenu
Registers a Lexical typeahead plugin for slash commands, mentions, and other triggered menus. Items
with `{ title, description, icon, keywords, command }` render in the default menu.
| Prop | Type | Default | Description |
| ------------- | -------------------------------------------------- | ------- | ----------------------------------------- |
| `char` | `string` | `"/"` | Trigger character |
| `items` | `({ query, editor }) => Item[] \| Promise- ` | - | Suggestion item loader |
| `children` | `(props) => ReactNode` | - | Custom renderer for complete menu control |
| `onSelect` | `(props) => void` | - | Selection handler for custom item shapes |
| `allowSpaces` | `boolean` | `false` | Allows spaces in the query |
| `maxHeight` | `number` | `384` | Max menu height in pixels |
### RichTextEditor.CharacterCount
Displays the editor's character and word counts.
| Prop | Type | Default | Description |
| ----------- | ----------------------------------- | ------- | --------------------------------------- |
| `showWords` | `boolean` | `false` | Appends word count to the default label |
| `children` | `ReactNode \| (stats) => ReactNode` | - | Custom count rendering |
### Hooks
| Hook | Description |
| ---------------------------------------------- | ------------------------------------------------------------------------------------ |
| `useRichTextEditor()` | Returns `{ editor, isDisabled, isReadOnly, maxLength }` from the nearest editor |
| `useRichTextEditorState(selector, equalityFn)` | Subscribes to selected Lexical editor state without re-rendering for unrelated edits |
# SearchField
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/search-field
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/search-field.mdx
> Search input field with clear button and search icon
## Import
```tsx
import { SearchField } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Label, SearchField} from "@darkcode-ui/react";
export function Basic() {
return (
Search
);
}
```
### Anatomy
```tsx
import {SearchField, Label, Description, FieldError} from '@darkcode-ui/react';
export default () => (
)
```
> **SearchField** allows users to enter and clear a search query. It includes a search icon and an optional clear button for easy reset.
### With Description
```tsx
import {Description, Label, SearchField} from "@darkcode-ui/react";
export function WithDescription() {
return (
Search products
Enter keywords to search for products
Search users
Search by name, email, or username
);
}
```
### Required Field
```tsx
import {Description, Label, SearchField} from "@darkcode-ui/react";
export function Required() {
return (
Search
Search query
Minimum 3 characters required
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
import {FieldError, Label, SearchField} from "@darkcode-ui/react";
export function Validation() {
return (
Search
Search query must be at least 3 characters
Search
Invalid characters in search query
);
}
```
### Disabled State
```tsx
import {Description, Label, SearchField} from "@darkcode-ui/react";
export function Disabled() {
return (
Search
This search field is disabled
Search
This search field is disabled
);
}
```
### Controlled
Control the value to synchronize with other components or perform custom formatting.
```tsx
"use client";
import {Button, Description, Label, SearchField} from "@darkcode-ui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("");
return (
Search
Current value: {value || "(empty)"}
setValue("")}>
Clear
setValue("example query")}>
Set example
);
}
```
### With Validation
Implement custom validation logic with controlled values.
```tsx
"use client";
import {Description, FieldError, Label, SearchField} from "@darkcode-ui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState("");
const isInvalid = value.length > 0 && value.length < 3;
return (
Search
{isInvalid ? (
Search query must be at least 3 characters
) : (
Enter at least 3 characters to search
)}
);
}
```
### Custom Icons
Customize the search icon and clear button icons.
```tsx
import {Description, Label, SearchField} from "@darkcode-ui/react";
export function CustomIcons() {
return (
Search (Custom Icons)
Custom icon children
);
}
```
### Full Width
```tsx
import {Label, SearchField} from "@darkcode-ui/react";
export function FullWidth() {
return (
Search
);
}
```
### Variants
The SearchField component supports two visual variants:
* **`primary`** (default) - Standard styling with shadow, suitable for most use cases
* **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
import {Label, SearchField} from "@darkcode-ui/react";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Description, Label, SearchField, Surface} from "@darkcode-ui/react";
export function OnSurface() {
return (
Search
Enter keywords to search
Advanced search
Use filters to refine your search
);
}
```
### Form Example
Complete form integration with validation and submission handling.
```tsx
"use client";
import {
Button,
Description,
FieldError,
Form,
Label,
SearchField,
Spinner,
} from "@darkcode-ui/react";
import React from "react";
export function FormExample() {
const [value, setValue] = React.useState("");
const [isSubmitting, setIsSubmitting] = React.useState(false);
const MIN_LENGTH = 3;
const isInvalid = value.length > 0 && value.length < MIN_LENGTH;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (value.length < MIN_LENGTH) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("Search submitted:", {query: value});
setValue("");
setIsSubmitting(false);
}, 1500);
};
return (
Search products
{isInvalid ? (
Search query must be at least {MIN_LENGTH} characters
) : (
Enter at least {MIN_LENGTH} characters to search
)}
{isSubmitting ? (
<>
Searching...
>
) : (
"Search"
)}
);
}
```
### With Keyboard Shortcut
Add keyboard shortcuts to quickly focus the search field.
```tsx
"use client";
import {Description, Kbd, Label, SearchField} from "@darkcode-ui/react";
import React from "react";
export function WithKeyboardShortcut() {
const inputRef = React.useRef(null);
const [value, setValue] = React.useState("");
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Check for Shift+S
if (e.shiftKey && e.key === "S" && !e.metaKey && !e.ctrlKey && !e.altKey) {
e.preventDefault();
inputRef.current?.focus();
}
// Check for ESC key to blur the input
if (e.key === "Escape" && document.activeElement === inputRef.current) {
inputRef.current?.blur();
}
};
// Add global event listener
window.addEventListener("keydown", handleKeyDown);
// Cleanup on unmount
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
return (
Search
Use keyboard shortcut to quickly focus this field
Press
S
to focus the search field
);
}
```
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **FieldError**: Inline validation messages for form fields
### Custom Render Function
```tsx
"use client";
import {Label, SearchField} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}>
Search
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {SearchField, Label} from '@darkcode-ui/react';
function CustomSearchField() {
return (
Search
);
}
```
### Customizing the component classes
SearchField uses CSS classes that can be customized. Override the component classes to match your design system.
```css
@layer components {
.search-field {
@apply flex flex-col gap-1;
}
/* When invalid, the description is hidden automatically */
.search-field[data-invalid],
.search-field[aria-invalid] {
[data-slot="description"] {
@apply hidden;
}
}
.search-field__group {
@apply bg-field text-field-foreground shadow-field rounded-field inline-flex h-9 items-center overflow-hidden border;
}
.search-field__input {
@apply flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
}
.search-field__search-icon {
@apply text-field-placeholder pointer-events-none shrink-0 ml-3 mr-0 size-4;
}
.search-field__clear-button {
@apply mr-1 shrink-0;
}
}
```
### CSS Classes
* `.search-field` – Root container with minimal styling (`flex flex-col gap-1`)
* `.search-field__group` – Container for search icon, input, and clear button with border and background styling
* `.search-field__input` – The search input field
* `.search-field__search-icon` – The search icon displayed on the left
* `.search-field__clear-button` – Button to clear the search field
* `.search-field--primary` – Primary variant with shadow (default)
* `.search-field--secondary` – Secondary variant without shadow, suitable for use in surfaces
> **Note:** Child components ([Label](/docs/components/label), [Description](/docs/components/description), [FieldError](/docs/components/field-error)) have their own CSS classes and styling. See their respective documentation for customization options.
### Interactive States
SearchField automatically manages these data attributes based on its state:
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` - Automatically hides the description slot when invalid
* **Disabled**: `[data-disabled="true"]` - Applied when `isDisabled` is true
* **Focus Within**: `[data-focus-within="true"]` - Applied when the input is focused
* **Focus Visible**: `[data-focus-visible="true"]` - Applied when focus is visible (keyboard navigation)
* **Hovered**: `[data-hovered="true"]` - Applied when hovering over the group
* **Empty**: `[data-empty="true"]` - Applied when the field is empty (hides clear button)
Additional attributes are available through render props (see SearchFieldRenderProps below).
## API Reference
### SearchField Props
SearchField inherits all props from React Aria's [SearchField](https://react-spectrum.adobe.com/react-aria/SearchField.html) component.
#### Base Props
| Prop | Type | Default | Description |
| ----------- | -------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `children` | `React.ReactNode \| (values: SearchFieldRenderProps) => React.ReactNode` | - | Child components (Label, Group, Input, etc.) or render function. |
| `className` | `string \| (values: SearchFieldRenderProps) => string` | - | CSS classes for styling, supports render props. |
| `style` | `React.CSSProperties \| (values: SearchFieldRenderProps) => React.CSSProperties` | - | Inline styles, supports render props. |
| `fullWidth` | `boolean` | `false` | Whether the search field should take full width of its container |
| `id` | `string` | - | The element's unique identifier. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
#### Value Props
| Prop | Type | Default | Description |
| -------------- | ------------------------- | ------- | -------------------------------------- |
| `value` | `string` | - | Current value (controlled). |
| `defaultValue` | `string` | - | Default value (uncontrolled). |
| `onChange` | `(value: string) => void` | - | Handler called when the value changes. |
#### Validation Props
| Prop | Type | Default | Description |
| -------------------- | ----------------------------------------------------------------- | ---------- | -------------------------------------------------------------- |
| `isRequired` | `boolean` | `false` | Whether user input is required before form submission. |
| `isInvalid` | `boolean` | - | Whether the value is invalid. |
| `validate` | `(value: string) => ValidationError \| true \| null \| undefined` | - | Custom validation function. |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA attributes. |
| `validationErrors` | `string[]` | - | Server-side validation errors. |
#### State Props
| Prop | Type | Default | Description |
| ------------ | --------- | ------- | -------------------------------------------------- |
| `isDisabled` | `boolean` | - | Whether the input is disabled. |
| `isReadOnly` | `boolean` | - | Whether the input can be selected but not changed. |
#### Form Props
| Prop | Type | Default | Description |
| ----------- | --------- | ------- | ---------------------------------------------------- |
| `name` | `string` | - | Name of the input element, for HTML form submission. |
| `autoFocus` | `boolean` | - | Whether the element should receive focus on render. |
#### Event Props
| Prop | Type | Default | Description |
| ---------- | ------------------------- | ------- | ------------------------------------------------------------ |
| `onSubmit` | `(value: string) => void` | - | Handler called when the user submits the search (Enter key). |
| `onClear` | `() => void` | - | Handler called when the clear button is pressed. |
#### Accessibility Props
| Prop | Type | Default | Description |
| ------------------ | -------- | ------- | ----------------------------------------------------- |
| `aria-label` | `string` | - | Accessibility label when no visible label is present. |
| `aria-labelledby` | `string` | - | ID of elements that label this field. |
| `aria-describedby` | `string` | - | ID of elements that describe this field. |
| `aria-details` | `string` | - | ID of elements with additional details. |
### Composition Components
SearchField works with these separate components that should be imported and used directly:
* **SearchField.Group** - Container for search icon, input, and clear button
* **SearchField.Input** - The search input field
* **SearchField.SearchIcon** - The search icon displayed on the left
* **SearchField.ClearButton** - Button to clear the search field
* **Label** - Field label component from `@darkcode-ui/react`
* **Description** - Helper text component from `@darkcode-ui/react`
* **FieldError** - Validation error message from `@darkcode-ui/react`
Each of these components has its own props API. Use them directly within SearchField for composition:
```tsx
Search
Enter keywords to search
Search query is required
```
#### SearchField.Group Props
SearchField.Group inherits props from React Aria's [Group](https://react-spectrum.adobe.com/react-aria/Group.html) component.
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------ | ------- | --------------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: GroupRenderProps) => React.ReactNode` | - | Child components (SearchIcon, Input, ClearButton) or render function. |
| `className` | `string \| (values: GroupRenderProps) => string` | - | CSS classes for styling. |
#### SearchField.Input Props
SearchField.Input inherits props from React Aria's [Input](https://react-spectrum.adobe.com/react-aria/Input.html) component.
| Prop | Type | Default | Description |
| ------------- | -------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `className` | `string` | - | CSS classes for styling. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the input. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `placeholder` | `string` | - | Placeholder text displayed when the input is empty. |
| `type` | `string` | `"search"` | Input type (automatically set to "search"). |
#### SearchField.SearchIcon Props
SearchField.SearchIcon is a custom component that renders the search icon.
| Prop | Type | Default | Description |
| ----------- | ----------------- | ---------------- | --------------------------------------------- |
| `children` | `React.ReactNode` | ` ` | Custom icon element. Defaults to search icon. |
| `className` | `string` | - | CSS classes for styling. |
#### SearchField.ClearButton Props
SearchField.ClearButton inherits props from React Aria's [Button](https://react-spectrum.adobe.com/react-aria/Button.html) component.
| Prop | Type | Default | Description |
| ----------- | ----------------- | ---------------------- | ------------------------------------------------------- |
| `children` | `React.ReactNode` | ` ` | Icon or content for the button. Defaults to close icon. |
| `className` | `string` | - | CSS classes for styling. |
| `slot` | `"clear"` | `"clear"` | Must be set to "clear" (automatically set). |
### SearchFieldRenderProps
When using render props with `className`, `style`, or `children`, these values are available:
| Prop | Type | Description |
| ---------------- | --------- | -------------------------------------------------------------------------- |
| `isDisabled` | `boolean` | Whether the field is disabled. |
| `isInvalid` | `boolean` | Whether the field is currently invalid. |
| `isReadOnly` | `boolean` | Whether the field is read-only. |
| `isRequired` | `boolean` | Whether the field is required. |
| `isFocused` | `boolean` | Whether the field is currently focused (DEPRECATED - use `isFocusWithin`). |
| `isFocusWithin` | `boolean` | Whether any child element is focused. |
| `isFocusVisible` | `boolean` | Whether focus is visible (keyboard navigation). |
| `value` | `string` | Current value. |
| `isEmpty` | `boolean` | Whether the field is empty. |
# TextArea
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/text-area
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/text-area.mdx
> Primitive multiline text input component that accepts standard HTML attributes
## Import
```tsx
import { TextArea } from '@darkcode-ui/react';
```
For validation, labels, and error messages, see **[TextField](/docs/components/text-field)**.
### Usage
```tsx
import {TextArea} from "@darkcode-ui/react";
export function Basic() {
return (
);
}
```
### Controlled
```tsx
"use client";
import {Description, TextArea} from "@darkcode-ui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("");
return (
setValue(event.target.value)}
/>
Characters: {value.length} / 280
);
}
```
### Rows and Resizing
```tsx
import {Label, TextArea} from "@darkcode-ui/react";
export function Rows() {
return (
Short feedback
Detailed notes
);
}
```
### Full Width
```tsx
import {TextArea} from "@darkcode-ui/react";
export function FullWidth() {
return (
);
}
```
### Variants
The TextArea component supports two visual variants:
* **`primary`** (default) - Standard styling with shadow, suitable for most use cases
* **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
import {TextArea} from "@darkcode-ui/react";
export function Variants() {
return (
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Surface, TextArea} from "@darkcode-ui/react";
export function OnSurface() {
return (
);
}
```
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **Input**: Single-line text input built on React Aria
* **Label**: Accessible label for form controls
## Styling
### Passing Tailwind CSS classes
```tsx
import {Label, TextArea} from '@darkcode-ui/react';
function CustomTextArea() {
return (
Message
);
}
```
### Customizing the component classes
Override the shared `.textarea` class once with Tailwind's `@layer components`.
```css
@layer components {
.textarea {
@apply rounded-xl border border-border bgsurface px-4 py-3 text-sm leading-6 shadow-sm;
&:hover,
&[data-hovered="true"] {
@apply bg-surface-secondary border-border/80;
}
&:focus-visible,
&[data-focus-visible="true"] {
@apply border-primary ring-2 ring-primary/20;
}
&[data-invalid="true"] {
@apply border-danger bg-danger-50/10 text-danger;
}
}
}
```
### CSS Classes
* `.textarea` – Underlying `` element styling
### Interactive States
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Focus Visible**: `:focus-visible` or `[data-focus-visible="true"]`
* **Invalid**: `[data-invalid="true"]`
* **Disabled**: `:disabled` or `[aria-disabled="true"]`
## API Reference
### TextArea Props
TextArea accepts all standard HTML `` attributes plus the following:
| Prop | Type | Default | Description |
| -------------- | --------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `className` | `string` | - | Tailwind classes merged with the base styles. |
| `rows` | `number` | `3` | Number of visible text lines. |
| `cols` | `number` | - | Visible width of the text control. |
| `value` | `string` | - | Controlled value for the textarea. |
| `defaultValue` | `string` | - | Initial uncontrolled value. |
| `onChange` | `(event: React.ChangeEvent) => void` | - | Change handler. |
| `placeholder` | `string` | - | Placeholder text. |
| `disabled` | `boolean` | `false` | Disables the textarea. |
| `readOnly` | `boolean` | `false` | Makes the textarea read-only. |
| `required` | `boolean` | `false` | Marks the textarea as required. |
| `name` | `string` | - | Name for form submission. |
| `autoComplete` | `string` | - | Autocomplete hint for the browser. |
| `maxLength` | `number` | - | Maximum number of characters. |
| `minLength` | `number` | - | Minimum number of characters. |
| `wrap` | `'soft' \| 'hard'` | - | How text wraps when submitted. |
| `fullWidth` | `boolean` | `false` | Whether the textarea should take full width of its container |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
> For validation props like `isInvalid`, `isRequired`, and error handling, use **[TextField](/docs/components/text-field)** with TextArea as a child component.
# TextField
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/text-field
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(forms)/text-field.mdx
> Composition-friendly text fields with labels, descriptions, and inline validation
## Import
```tsx
import { TextField } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Input, Label, TextField} from "@darkcode-ui/react";
export function Basic() {
return (
Email
);
}
```
### Anatomy
```tsx
import {TextField, Label, Input, Description, FieldError} from '@darkcode-ui/react';
export default () => (
)
```
> **TextField** combines label, input, description, and error into a single accessible component.
> For standalone inputs, use **[Input](/docs/components/input)** or **[TextArea](/docs/components/textarea)**.
### With Description
```tsx
import {Description, Input, Label, TextField} from "@darkcode-ui/react";
export function WithDescription() {
return (
Username
Choose a unique username for your account
);
}
```
### Required Field
```tsx
import {Description, Input, Label, TextField} from "@darkcode-ui/react";
export function Required() {
return (
Full Name
This field is required
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
"use client";
import {Description, FieldError, Input, Label, TextArea, TextField} from "@darkcode-ui/react";
import React from "react";
export function Validation() {
const [username, setUsername] = React.useState("");
const [bio, setBio] = React.useState("");
const isUsernameInvalid = username.length > 0 && username.length < 3;
const isBioInvalid = bio.length > 0 && bio.length < 20;
return (
Username
{isUsernameInvalid ? (
Username must be at least 3 characters.
) : (
Choose a unique username for your profile.
)}
Bio
{isBioInvalid ? (
Bio must contain at least 20 characters.
) : (
Minimum 20 characters ({bio.length}/20).
)}
);
}
```
### Controlled
Control the value to synchronize counters, previews, or formatting.
```tsx
"use client";
import {Description, Input, Label, TextArea, TextField} from "@darkcode-ui/react";
import React from "react";
export function Controlled() {
const [name, setName] = React.useState("");
const [bio, setBio] = React.useState("");
return (
Display name
Characters: {name.length}
Bio
Characters: {bio.length} / 200
);
}
```
### Error Message
```tsx
import {FieldError, Input, Label, TextField} from "@darkcode-ui/react";
export function WithError() {
return (
Email
Please enter a valid email address
);
}
```
### Disabled State
```tsx
import {Description, Input, Label, TextField} from "@darkcode-ui/react";
export function Disabled() {
return (
Account ID
This field cannot be edited
);
}
```
### TextArea
Use [TextArea](/docs/components/textarea) instead of [Input](/docs/components/input) for multiline content.
```tsx
import {Description, Label, TextArea, TextField} from "@darkcode-ui/react";
export function TextAreaExample() {
return (
Message
Maximum 500 characters
);
}
```
### Input Types
```tsx
import {Input, Label, TextField} from "@darkcode-ui/react";
export function InputTypes() {
return (
Password
Age
Email
Website
Phone
);
}
```
### Full Width
```tsx
import {FieldError, Input, Label, TextField} from "@darkcode-ui/react";
export function FullWidth() {
return (
Your name
Password
Password must be longer than 8 characters
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` on Input or TextArea components to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Description, Input, Label, Surface, TextArea, TextField} from "@darkcode-ui/react";
export function OnSurface() {
return (
Your name
We'll never share this with anyone else
Email
Bio
Minimum 4 rows
);
}
```
## Related Components
* **Input**: Single-line text input built on React Aria
* **TextArea**: Multiline text input with focus management
* **Fieldset**: Group related form controls with legends
### Custom Render Function
```tsx
"use client";
import {Input, Label, TextField} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}
type="email"
>
Email
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {TextField, Label, Input, Description} from '@darkcode-ui/react';
function CustomTextField() {
return (
Project name
Keep it short and memorable.
);
}
```
### Customizing the component classes
TextField has minimal default styling. Override the `.textfield` class to customize the container styling.
```css
@layer components {
.textfield {
@apply flex flex-col gap-1;
}
/* When invalid, the description is hidden automatically */
.textfield[data-invalid="true"] [data-slot="description"],
.textfield[aria-invalid="true"] [data-slot="description"] {
@apply hidden;
}
/* Description has default padding */
.textfield [data-slot="description"] {
@apply px-1;
}
}
```
### CSS Classes
* `.textfield` – Root container with minimal styling (`flex flex-col gap-1`)
> **Note:** Child components ([Label](/docs/components/label), [Input](/docs/components/input), [TextArea](/docs/components/textarea), [Description](/docs/components/description), [FieldError](/docs/components/field-error)) have their own CSS classes and styling. See their respective documentation for customization options.
### Interactive States
TextField automatically manages these data attributes based on its state:
* **Invalid**: `[data-invalid="true"]` or `[aria-invalid="true"]` - Automatically hides the description slot when invalid
* **Disabled**: `[data-disabled="true"]` - Applied when `isDisabled` is true
* **Focus Within**: `[data-focus-within="true"]` - Applied when any child input is focused
* **Focus Visible**: `[data-focus-visible="true"]` - Applied when focus is visible (keyboard navigation)
Additional attributes are available through render props (see TextFieldRenderProps below).
## API Reference
### TextField Props
TextField inherits all props from React Aria's [TextField](https://react-spectrum.adobe.com/react-aria/TextField.html) component.
#### Base Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: TextFieldRenderProps) => React.ReactNode` | - | Child components (Label, Input, etc.) or render function. |
| `className` | `string \| (values: TextFieldRenderProps) => string` | - | CSS classes for styling, supports render props. |
| `style` | `React.CSSProperties \| (values: TextFieldRenderProps) => React.CSSProperties` | - | Inline styles, supports render props. |
| `fullWidth` | `boolean` | `false` | Whether the text field should take full width of its container |
| `id` | `string` | - | The element's unique identifier. |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
#### Validation Props
| Prop | Type | Default | Description |
| -------------------- | ----------------------------------------------------------------- | ---------- | -------------------------------------------------------------- |
| `isRequired` | `boolean` | `false` | Whether user input is required before form submission. |
| `isInvalid` | `boolean` | - | Whether the value is invalid. |
| `validate` | `(value: string) => ValidationError \| true \| null \| undefined` | - | Custom validation function. |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA attributes. |
| `validationErrors` | `string[]` | - | Server-side validation errors. |
#### Value Props
| Prop | Type | Default | Description |
| -------------- | ------------------------- | ------- | -------------------------------------- |
| `value` | `string` | - | Current value (controlled). |
| `defaultValue` | `string` | - | Default value (uncontrolled). |
| `onChange` | `(value: string) => void` | - | Handler called when the value changes. |
#### State Props
| Prop | Type | Default | Description |
| ------------ | --------- | ------- | -------------------------------------------------- |
| `isDisabled` | `boolean` | - | Whether the input is disabled. |
| `isReadOnly` | `boolean` | - | Whether the input can be selected but not changed. |
#### Form Props
| Prop | Type | Default | Description |
| ----------- | --------- | ------- | ---------------------------------------------------- |
| `name` | `string` | - | Name of the input element, for HTML form submission. |
| `autoFocus` | `boolean` | - | Whether the element should receive focus on render. |
#### Accessibility Props
| Prop | Type | Default | Description |
| ------------------ | -------- | ------- | ----------------------------------------------------- |
| `aria-label` | `string` | - | Accessibility label when no visible label is present. |
| `aria-labelledby` | `string` | - | ID of elements that label this field. |
| `aria-describedby` | `string` | - | ID of elements that describe this field. |
| `aria-details` | `string` | - | ID of elements with additional details. |
### Composition Components
TextField works with these separate components that should be imported and used directly:
* **Label** - Field label component from `@darkcode-ui/react`
* **Input** - Single-line text input from `@darkcode-ui/react`
* **TextArea** - Multi-line text input from `@darkcode-ui/react`
* **Description** - Helper text component from `@darkcode-ui/react`
* **FieldError** - Validation error message from `@darkcode-ui/react`
Each of these components has its own props API. Use them directly within TextField for composition:
```tsx
Email Address
setEmail(e.target.value)} />
We'll never share your email.
Please enter a valid email address.
```
### TextFieldRenderProps
When using render props with `className`, `style`, or `children`, these values are available:
| Prop | Type | Description |
| ---------------- | --------- | -------------------------------------------------------------------------- |
| `isDisabled` | `boolean` | Whether the field is disabled. |
| `isInvalid` | `boolean` | Whether the field is currently invalid. |
| `isReadOnly` | `boolean` | Whether the field is read-only. |
| `isRequired` | `boolean` | Whether the field is required. |
| `isFocused` | `boolean` | Whether the field is currently focused (DEPRECATED - use `isFocusWithin`). |
| `isFocusWithin` | `boolean` | Whether any child element is focused. |
| `isFocusVisible` | `boolean` | Whether focus is visible (keyboard navigation). |
# Action Bar
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/action-bar
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(layout)/action-bar.mdx
> A floating contextual toolbar that surfaces bulk actions for the current selection.
## Import
```tsx
import { ActionBar } from '@darkcode-ui/react';
```
## Usage
`ActionBar` is a floating toolbar that appears when one or more items are selected. Render it
conditionally — or pass `isOpen` — alongside a selectable collection such as
[ListView](/react/components/list-view) or [Table](/react/components/table).
```tsx
"use client";
import {ActionBar} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function Basic() {
return (
}
>
Share
}>
Move
}
variant="danger-soft"
>
Delete
);
}
```
## Anatomy
Compose the bar from a selection count, a group of actions, and a clear button. Use
`ActionBar.Action` for each action and `ActionBar.Separator` to visually group regions. Wire a
single `onClose` handler on the root — `ActionBar.Close` and the Escape key both use it.
```tsx
import { ActionBar } from '@darkcode-ui/react';
0} onClose={clearSelection}>
}>Share
} variant="danger-soft">Delete
```
## With List View
Drive the bar's visibility from a controlled selection. The bar animates in when the selection
becomes non-empty, and animates back out when it is cleared.
```tsx
"use client";
import type {Selection} from "@darkcode-ui/react";
import {ActionBar, ListView} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
const files = [
{icon: "gravity-ui:file-text", id: "1", name: "Project Proposal.pdf", size: "2.4 MB"},
{icon: "gravity-ui:picture", id: "2", name: "Design Mockups.fig", size: "8.1 MB"},
{icon: "gravity-ui:file", id: "3", name: "Q4 Financial Report.xlsx", size: "1.2 MB"},
{icon: "gravity-ui:picture", id: "4", name: "Team Photo.png", size: "5.7 MB"},
];
export function WithListView() {
const [selected, setSelected] = useState(new Set());
const count = selected === "all" ? files.length : selected.size;
return (
<>
{(file) => (
{file.name}
{file.size}
)}
0} onClose={() => setSelected(new Set())}>
}
>
Share
}
variant="danger-soft"
>
Delete
>
);
}
```
## Overflow
When the actions don't fit, the trailing ones collapse into a "More" menu. Only `ActionBar.Action`
children participate — their `icon`, label, `onPress`, and `isDisabled` are reused to build the
menu items. Use `maxVisible` on `ActionBar.Actions` to cap the inline count, or let the bar measure
the available width and collapse responsively.
```tsx
"use client";
import {ActionBar} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function Overflow() {
return (
}
>
Share
}>
Move
}>
Copy
}>
Tag
}
variant="danger-soft"
>
Delete
);
}
```
## Position & inline
By default the bar is fixed to the bottom-center of the viewport. Use `position="top"` to pin it to
the top, or `inline` to anchor it within its container — useful when embedding the bar inside a
panel or card. The enter/exit slide follows the placement.
```tsx
"use client";
import {ActionBar} from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
export function Inline() {
return (
Panel content…
}
>
Share
}
variant="danger-soft"
>
Delete
);
}
```
## CSS Classes
### Base Classes
* `.action-bar` — Positioning container. Fixed, centered, `pointer-events: none`.
* `.action-bar--top` — Pins the bar to the top edge.
* `.action-bar--inline` — Anchors the bar within its container instead of the viewport.
* `.action-bar__content` — The pill surface (`role="toolbar"`). Captures pointer events; uses the overlay background and shadow. Enter/exit animations are driven by `data-entering` / `data-exiting` and the placement via `data-position`.
### Element Classes
* `.action-bar__selection-count` — Leading selection count label.
* `.action-bar__actions` — Group wrapping the action buttons.
* `.action-bar__action` — An individual action button.
* `.action-bar__more` — The overflow "More" trigger.
* `.action-bar__separator` — Vertical divider between regions.
* `.action-bar__close` — Trailing clear-selection button.
## API Reference
### ActionBar
The positioning root.
| Prop | Type | Default | Description |
| ----------- | ------------------- | ---------- | ------------------------------------------------------------------------ |
| `isOpen` | `boolean` | `true` | When `false`, the bar animates out and then unmounts. |
| `onClose` | `() => void` | — | Called when dismissed via the clear button or the Escape key. |
| `position` | `"bottom" \| "top"` | `"bottom"` | Which viewport edge the bar is pinned to. |
| `inline` | `boolean` | `false` | Anchor the bar within its container instead of the viewport. |
| `autoFocus` | `boolean` | `false` | Move focus into the toolbar when it opens. |
| `className` | `string` | — | Additional CSS classes. |
| `children` | `ReactNode` | — | Typically a single `ActionBar.Content`. |
### ActionBar.Content
The visible toolbar pill. Built on React Aria's `Toolbar`, so it provides `role="toolbar"`, roving
focus, and arrow-key navigation. Accepts an `aria-label` (defaults to `"Selection actions"`).
### ActionBar.SelectionCount
Leading selection summary.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | --------------------------------------------------------- |
| `count` | `number` | — | Renders `{count} selected` when no children are provided. |
| `children` | `ReactNode` | — | Custom content overriding the default count text. |
### ActionBar.Actions
Container for the actions. Collapses overflowing `ActionBar.Action` children into a "More" menu.
| Prop | Type | Default | Description |
| --------------- | -------- | ---------------- | ------------------------------------------------------------------ |
| `maxVisible` | `number` | — | Hard cap on inline actions before the rest collapse into the menu. |
| `overflowLabel` | `string` | `"More actions"` | Accessible label for the overflow trigger. |
### ActionBar.Action
A single action. Composes [Button](/react/components/button) (defaults to `size="sm"`,
`variant="ghost"`) and forwards its props.
| Prop | Type | Default | Description |
| ---------- | --------------- | --------- | --------------------------------------------------------------- |
| `icon` | `ReactNode` | — | Leading icon rendered before the label. |
| `children` | `ReactNode` | — | The action label. Reused as the menu item text when collapsed. |
| `variant` | `ButtonVariant` | `"ghost"` | The button variant (e.g. `"danger-soft"`). |
| `onPress` | `(e) => void` | — | Press handler, also invoked when chosen from the overflow menu. |
### ActionBar.Separator
Vertical divider with `role="separator"`.
### ActionBar.Close
A clear-selection button. Composes [CloseButton](/react/components/button) and forwards all of
its props. Defaults its `onPress` to the root's `onClose` when none is provided.
## Related Components
* **List View**: Single-column interactive list with selection and item actions
* **Table**: Structured data display in rows and columns
* **Button**: Allows a user to perform an action
## Accessibility
* The content region is a React Aria `Toolbar`: it uses `role="toolbar"` with an accessible label, manages a roving tab stop, and supports ← / → arrow-key navigation between actions.
* Escape calls `onClose`. When the overflow menu is open it consumes Escape first, so the bar is only dismissed once focus returns to the toolbar.
* Set `autoFocus` to move focus into the bar when it opens; focus is restored to the previously focused element when it closes.
* Animations respect `prefers-reduced-motion` — the bar appears and disappears instantly when reduced motion is requested.
* Keep the action set short and ensure each action has a visible label or `aria-label`.
# Card
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/card
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(layout)/card.mdx
> Flexible container component for grouping related content and actions
## Import
```tsx
import { Card } from "@darkcode-ui/react";
```
### Usage
```tsx
import {Card, Link} from "@darkcode-ui/react";
import {CircleDollar} from "@gravity-ui/icons";
export function Default() {
return (
Become an Acme Creator!
Visit the Acme Creator Hub to sign up today and start earning credits from your fans and
followers.
Creator Hub
);
}
```
### Anatomy
Import the Card component and access all parts using dot notation.
```tsx
import { Card } from "@darkcode-ui/react";
export default () => (
);
```
### Variants
Cards come in semantic variants that describe their prominence level rather than specific visual styles. This allows themes to interpret them differently:
```tsx
import {Card} from "@darkcode-ui/react";
export function Variants() {
return (
Transparent
Minimal prominence with transparent background
Use for less important content or nested cards
Default
Standard card appearance (bg-surface)
The default card variant for most use cases
Secondary
Medium prominence (bg-surface-secondary)
Use to draw moderate attention
Tertiary
Higher prominence (bg-surface-tertiary)
Use for primary or featured content
);
}
```
* **`transparent`** - Minimal prominence, transparent background (great for nested cards)
* **`default`** - Standard card for most use cases (surface-secondary)
* **`secondary`** - Medium prominence to draw moderate attention (surface-tertiary)
* **`tertiary`** - Higher prominence for important content (surface-tertiary)
### Horizontal Layout
```tsx
import {Button, Card, CloseButton} from "@darkcode-ui/react";
export function Horizontal() {
return (
Become an ACME Creator!
Lorem ipsum dolor sit amet consectetur. Sed arcu donec id aliquam dolor sed amet
faucibus etiam.
Only 10 spots
Submission ends Oct 10.
Apply Now
);
}
```
### With Avatar
```tsx
import {Avatar, Card} from "@darkcode-ui/react";
export function WithAvatar() {
return (
Indie Hackers
148 members
IH
By Martha
AI Builders
362 members
B
By John
);
}
```
### With Images
```tsx
import {Avatar, Button, Card, CloseButton, Link} from "@darkcode-ui/react";
import {CircleDollar} from "@gravity-ui/icons";
export function WithImages() {
return (
{/* Row 1: Large Product Card - Available Soon */}
Become an ACME Creator!
Lorem ipsum dolor sit amet consectetur. Sed arcu donec id aliquam dolor sed amet
faucibus etiam.
Only 10 spots
Submission ends Oct 10.
Apply Now
{/* Row 2 */}
{/* Left Column */}
{/* Top Card */}
PAYMENT
You can now withdraw on crypto
Add your wallet in settings to withdraw
Go to settings
{/* Bottom cards */}
{/* Left Card */}
JK
Indie Hackers
148 members
JK
By John
{/* Right Card */}
AB
AI Builders
362 members
M
By Martha
{/* Right Column */}
{/* Background image */}
{/* Header */}
NEO
Home Robot
{/* Footer */}
Available soon
Get notified
Notify me
{/* Row 3 */}
{/* Left Column: Card */}
Get now
{/* Right Column: Cards Stack */}
{/* 1 */}
Bridging the Future
Today, 6:30 PM
{/* 2 */}
Avocado Hackathon
Wed, 4:30 PM
{/* 3 */}
Sound Electro | Beyond art
Fri, 8:00 PM
);
}
```
### With Form
```tsx
"use client";
import {Button, Card, Form, Input, Label, Link, TextField} from "@darkcode-ui/react";
export function WithForm() {
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
// Convert FormData to plain object
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert("Form submitted successfully!");
};
return (
Login
Enter your credentials to access your account
Email
Password
Sign In
Forgot password?
);
}
```
## Accessibility
```tsx
import { Card } from '@darkcode-ui/react';
import { cardVariants } from '@darkcode-ui/styles';
// Semantic markup
Article Title
// Interactive cards
Product Name
```
## Related Components
* **Surface**: Base container surface
* **Avatar**: Display user profile images
* **Form**: Form validation and submission handling
## Styling
### Component Customization
```tsx
Custom Styled Card
Custom colors applied
Content with custom styling
```
### CSS Variable Overrides
```css
/* Override specific variants */
.card--secondary {
@apply bg-gradient-to-br from-blue-50 to-purple-50;
}
/* Custom element styles */
.card__title {
@apply text-xl font-bold;
}
```
## CSS Classes
Card uses [BEM](https://getbem.com/) naming for predictable styling, ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/card.css)):
#### Base Classes
* `.card` - Base container with padding and border
* `.card__header` - Header section container
* `.card__title` - Title with base font size and weight
* `.card__description` - Muted description text
* `.card__content` - Flexible content container
* `.card__footer` - Footer with row layout
#### Variant Classes
* `.card--transparent` - Minimal prominence, transparent background (maps to `transparent` variant)
* `.card--default` - Standard appearance with surface-secondary (default)
* `.card--secondary` - Medium prominence with surface-tertiary (maps to `secondary` variant)
* `.card--tertiary` - Higher prominence with surface-tertiary (maps to `tertiary` variant)
## API Reference
### Card
| Prop | Type | Default | Description |
| ----------- | --------------------------------------------------------- | ----------- | -------------------------------------------- |
| `variant` | `"transparent" \| "default" \| "secondary" \| "tertiary"` | `"default"` | Semantic variant indicating prominence level |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Card content |
### Card.Header
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Header content |
### Card.Title
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Title content (renders as `h3`) |
### Card.Description
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ------------------------------------ |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Description content (renders as `p`) |
### Card.Content
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Main content |
### Card.Footer
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Footer content |
# Resizable
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/resizable
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(layout)/resizable.mdx
> Resizable panel groups with composable handle types and variants
## About
The `Resizable` component is built on top of [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels)
by [bvaughn](https://github.com/bvaughn), wired up with DarkCode UI design tokens and the
compound-component pattern.
## Import
```tsx
import { Resizable } from '@darkcode-ui/react';
```
## Anatomy
Wrap any number of `Resizable.Panel` children in a `Resizable` group, separated by
`Resizable.Handle`s. The group sizes each panel as a percentage of its own width (horizontal)
or height (vertical).
```tsx
Sidebar
Main content
```
Every layer is composable: `Resizable.Handle` renders the drag separator, and
`Resizable.Indicator` is the inner affordance (pill or drag dots) you can also compose manually.
### Usage
```tsx
import {Resizable} from "@darkcode-ui/react";
export function ResizableBasic() {
return (
);
}
```
## Vertical
Set `orientation="vertical"` to stack panels and resize along the Y axis.
```tsx
import {Resizable} from "@darkcode-ui/react";
export function ResizableVertical() {
return (
);
}
```
## Handle Types
Three handle affordances — use the one that matches the importance of the boundary.
* `line` — minimal 1px separator. Default. Works everywhere.
* `drag` — 1px separator + drag-grip chip in the middle. Use when users may not realize the boundary is resizable.
* `pill` — 1px separator + rounded pill grip. Highest affordance. Use sparingly, usually for critical boundaries.
* `handle` — standalone pill grip with **no separator line**. Use when you want a drag affordance that floats between panels without a continuous divider.
```tsx
import {Resizable} from "@darkcode-ui/react";
const types = ["line", "drag", "pill", "handle"] as const;
export function ResizableHandleTypes() {
return (
{types.map((type) => (
))}
);
}
```
## Variants
Variants map directly to separator tokens so the handle blends into the surface it sits on.
* `primary` → `separator` (lightest — default, works on the primary background)
* `secondary` → `separator-secondary` (for `background-secondary` / `surface`)
* `tertiary` → `separator-tertiary` (for darker tertiary surfaces)
```tsx
import {Resizable} from "@darkcode-ui/react";
const variants = ["primary", "secondary", "tertiary"] as const;
export function ResizableVariants() {
return (
{variants.map((variant) => (
))}
);
}
```
## Nested Groups
Panels can contain nested `Resizable` groups. Use different orientations to build editor-style
three-pane layouts. Give each nested group a unique `id`.
```tsx
import {Resizable} from "@darkcode-ui/react";
export function ResizableNested() {
return (
);
}
```
## Collapsible Panels
Set `collapsible` and `collapsedSize` on a panel to let it collapse when dragged below its
minimum size. Use `handleRef` for imperative collapse/expand.
```tsx
"use client";
import type {ImperativePanelHandle} from "@darkcode-ui/react";
import {Button, Resizable} from "@darkcode-ui/react";
import {useRef, useState} from "react";
export function ResizableCollapsible() {
const panelRef = useRef(null);
const [collapsed, setCollapsed] = useState(false);
return (
{
const panel = panelRef.current;
if (!panel) return;
if (panel.isCollapsed()) panel.expand();
else panel.collapse();
}}
>
{collapsed ? "Expand sidebar" : "Collapse sidebar"}
setCollapsed(true)}
onExpand={() => setCollapsed(false)}
>
Sidebar
Main content
);
}
```
## With Indicator
Set `withIndicator` on a `line`-type handle to render the drag-dots affordance inline, or compose
`Resizable.Indicator` directly for full control.
```tsx
import {Resizable} from "@darkcode-ui/react";
export function ResizableWithIndicator() {
return (
);
}
```
## Persisted Sizes
### Client-only apps
Pass `autoSaveId` to persist panel sizes to `localStorage`. Reloads restore the previous layout
automatically. This is powered by `react-resizable-panels` and works out of the box for
client-only apps (Vite, Storybook, etc.).
```tsx
Sidebar
Main
```
### SSR-friendly persistence with cookies
On SSR frameworks, `localStorage` isn't available on the server, so the first paint uses the
default sizes and the client re-renders after hydration. To avoid this flash, use
`createCookieStorage` — a built-in helper that returns a cookie-backed `PanelGroupStorage`. Pass
it to the `storage` prop alongside `autoSaveId`:
```tsx
import {createCookieStorage, Resizable} from '@darkcode-ui/react';
const storage = createCookieStorage();
export function ResizableLayout() {
return (
Sidebar
Main
);
}
```
Reads prefer the cookie (so the server can forward saved layouts) and fall back to
`localStorage` for client-only apps; writes go to both so cross-tab readers stay in sync.
#### Next.js App Router
On the server, read the cookie and forward the saved layout to each panel via `defaultSize`:
```tsx
// app/layout.tsx
import {cookies} from 'next/headers';
export default async function Layout({children}: {children: React.ReactNode}) {
const store = await cookies();
const layout = store.get('react-resizable-panels:app:panels');
let defaultSizes = [30, 70];
if (layout?.value) {
try {
const parsed = JSON.parse(layout.value);
if (Array.isArray(parsed)) defaultSizes = parsed;
} catch {
// ignore malformed cookie
}
}
return {children} ;
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {Resizable} from '@darkcode-ui/react';
function CustomResizable() {
return (
Sidebar
Main
);
}
```
### Customizing the component classes
To customize the Resizable component classes, you can use the `@layer components` directive.
```css
@layer components {
.resizable__handle {
--resizable-handle-hit-area: 12px;
}
.resizable__indicator--pill {
@apply bg-accent-soft;
}
}
```
### CSS Classes
The Resizable component uses these CSS classes:
#### Base Classes
* `.resizable` - Root panel group
* `.resizable--horizontal` / `.resizable--vertical` - Orientation modifiers
* `.resizable__panel` - Individual panel
#### Handle Classes
* `.resizable__handle` - Resize handle (separator)
* `.resizable__handle--line` / `--drag` / `--pill` / `--handle` - Handle type modifiers
* `.resizable__handle--primary` / `--secondary` / `--tertiary` - Emphasis modifiers
#### Indicator Classes
* `.resizable__indicator` - Grip affordance
* `.resizable__indicator--pill` - Rounded pill grip
* `.resizable__indicator--drag` - Drag-dots grip
#### Data Attributes
* `[data-orientation]` on the root — `horizontal` or `vertical`
* `[data-resize-handle-state]` on the handle — `inactive`, `hover`, or `drag`
* `[data-disabled="true"]` on a disabled handle
## CSS Variables
The root `.resizable` element exposes the following variables for easy customization:
| Variable | Default | Description |
| ----------------------------------- | ---------------------------------- | ------------------------------------ |
| `--resizable-handle-size` | `1px` | Visible separator thickness. |
| `--resizable-handle-hit-area` | `8px` | Invisible grab area around the line. |
| `--resizable-handle-color` | `var(--color-separator)` | Default handle color. |
| `--resizable-handle-color-hover` | `var(--color-separator-secondary)` | Hover color. |
| `--resizable-handle-color-active` | `var(--color-accent-soft)` | Active/dragging color. |
| `--resizable-indicator-pill-width` | `6px` | Pill indicator short axis. |
| `--resizable-indicator-pill-height` | `32px` | Pill indicator long axis. |
| `--resizable-indicator-drag-size` | `12px` | Drag dots size. |
## API Reference
### Resizable
The root panel group. Wraps `react-resizable-panels`' `PanelGroup`.
| Prop | Type | Default | Description |
| ------------- | --------------------------------- | -------------- | -------------------------------------------------------- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. |
| `autoSaveId` | `string` | — | Unique id used to persist panel sizes to `localStorage`. |
| `onLayout` | `(sizes: number[]) => void` | — | Fires on every layout change (including while dragging). |
| `storage` | `PanelGroupStorage` | `localStorage` | Custom storage implementation for SSR-safe persistence. |
| `id` | `string` | — | Unique identifier (required for nested groups). |
| `handleRef` | `Ref` | — | Imperative handle (`setLayout`, `getLayout`). |
### Resizable.Panel
| Prop | Type | Default | Description |
| --------------- | ---------------------------- | ------- | ------------------------------------------------------- |
| `defaultSize` | `number` | — | Initial panel size (percent). |
| `minSize` | `number` | — | Minimum size (percent). |
| `maxSize` | `number` | — | Maximum size (percent). |
| `collapsible` | `boolean` | `false` | Whether the panel can collapse to `collapsedSize`. |
| `collapsedSize` | `number` | — | Size applied when collapsed. |
| `id` | `string` | — | Unique id (required with `autoSaveId` + `collapsible`). |
| `order` | `number` | — | Panel render order (for conditional rendering). |
| `handleRef` | `Ref` | — | Imperative handle (`collapse`, `expand`, `resize`). |
| `onCollapse` | `() => void` | — | Fires when the panel collapses. |
| `onExpand` | `() => void` | — | Fires when the panel expands. |
| `onResize` | `(size: number) => void` | — | Fires on resize. |
### Resizable.Handle
| Prop | Type | Default | Description |
| --------------- | ---------------------------------------- | ----------- | -------------------------------------------------------------------------------- |
| `type` | `"line" \| "drag" \| "pill" \| "handle"` | `"line"` | Affordance style. `handle` is a standalone pill grip without the separator line. |
| `variant` | `"primary" \| "secondary" \| "tertiary"` | `"primary"` | Emphasis level. Maps to separator tokens. |
| `withIndicator` | `boolean` | — | Sugar: render the default indicator inside the handle. |
| `disabled` | `boolean` | `false` | Whether the handle is disabled. |
| `onDragging` | `(isDragging: boolean) => void` | — | Fires when drag starts/ends. |
### Resizable.Indicator
| Prop | Type | Default | Description |
| ---------- | ------------------ | -------- | ------------------------------------------------- |
| `type` | `"pill" \| "drag"` | `"pill"` | Affordance style when no children are provided. |
| `children` | `ReactNode` | — | Custom content. Overrides the default affordance. |
### createCookieStorage
`createCookieStorage(options?)` returns a cookie-backed `PanelGroupStorage`.
| Option | Type | Default | Description |
| ---------- | ----------------------------- | ---------- | --------------------------- |
| `maxAge` | `number` | `31536000` | Cookie lifetime in seconds. |
| `path` | `string` | `"/"` | Cookie path. |
| `sameSite` | `"Lax" \| "Strict" \| "None"` | `"Lax"` | SameSite policy. |
# Separator
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/separator
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(layout)/separator.mdx
> Visually divide content sections
## Import
```tsx
import { Separator } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Separator} from "@darkcode-ui/react";
export function Basic() {
return (
DarkCode UI Components
Beautiful, fast and modern React UI library.
);
}
```
### Vertical
```tsx
import {Separator} from "@darkcode-ui/react";
export function Vertical() {
return (
);
}
```
### With Content
```tsx
import {Separator} from "@darkcode-ui/react";
const items = [
{
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/bell-small.png",
subtitle: "Receive account activity updates",
title: "Set Up Notifications",
},
{
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/compass-small.png",
subtitle: "Connect your browser to your account",
title: "Set up Browser Extension",
},
{
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/mint-collective-small.png",
subtitle: "Create your first collectible",
title: "Mint Collectible",
},
];
export function WithContent() {
return (
{items.map((item, index) => (
{item.title}
{item.subtitle}
{index < items.length - 1 &&
}
))}
);
}
```
### Variants
```tsx
import {Separator} from "@darkcode-ui/react";
export function Variants() {
return (
Default Variant
Secondary Variant
Tertiary Variant
);
}
```
### With Surface
The Separator component adapts to different surface backgrounds for better visibility.
```tsx
import {Separator, Surface} from "@darkcode-ui/react";
export function WithSurface() {
return (
Default Surface
Surface Content
Secondary Surface
Surface Content
Tertiary Surface
Surface Content
Transparent Surface
Surface Content
);
}
```
## Related Components
* **Card**: Content container with header, body, and footer
* **Chip**: Compact elements for tags and filters
* **Avatar**: Display user profile images
### Custom Render Function
```tsx
"use client";
import {Separator} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
DarkCode UI Components
Beautiful, fast and modern React UI library.
} />
Blog
}
/>
Docs
}
/>
Source
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {Separator} from '@darkcode-ui/react';
function CustomSeparator() {
return (
);
}
```
### Customizing the component classes
To customize the Separator component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.separator {
@apply bg-accent h-[2px];
}
.separator--vertical {
@apply bg-accent w-[2px];
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Separator component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/separator.css)):
#### Base & Orientation Classes
* `.separator` - Base separator styles with default horizontal orientation
* `.separator--horizontal` - Horizontal orientation (full width, 1px height)
* `.separator--vertical` - Vertical orientation (full height, 1px width)
#### Variant Classes
* `.separator--default` - Default variant with standard contrast
* `.separator--secondary` - Secondary variant with medium contrast
* `.separator--tertiary` - Tertiary variant with subtle contrast
## API Reference
### Separator Props
| Prop | Type | Default | Description |
| ------------- | ----------------------------------------------------------------- | -------------- | ---------------------------------------------------------------- |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | The orientation of the separator |
| `variant` | `'default' \| 'secondary' \| 'tertiary'` | `'default'` | The visual variant of the separator |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
# Surface
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/surface
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(layout)/surface.mdx
> Container component that provides surface-level styling and context for child components
## Import
```tsx
import { Surface } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Surface} from "@darkcode-ui/react";
export function Variants() {
return (
Default
Surface Content
This is a default surface variant. It uses bg-surface styling.
Secondary
Surface Content
This is a secondary surface variant. It uses bg-surface-secondary styling.
Tertiary
Surface Content
This is a tertiary surface variant. It uses bg-surface-tertiary styling.
Transparent
Surface Content
This is a transparent surface variant. It has no background, suitable for overlays and
cards with custom backgrounds.
);
}
```
## Overview
The Surface component is a semantic container that provides different levels of visual prominence through variants.
### Variants
Surface comes in semantic variants that describe their prominence level:
* **`default`** - Standard surface appearance (bg-surface)
* **`secondary`** - Medium prominence (bg-surface-secondary)
* **`tertiary`** - Higher prominence (bg-surface-tertiary)
## Usage with Form Components
When using form components inside a Surface, use the `variant="secondary"` prop to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import { Surface, Input, TextArea } from '@darkcode-ui/react';
function App() {
return (
);
}
```
## Related Components
* **CheckboxGroup**: Group of checkboxes with shared state
* **Fieldset**: Group related form controls with legends
* **InputOTP**: One-time password input
## Styling
### Passing Tailwind CSS classes
```tsx
import { Surface } from '@darkcode-ui/react';
function CustomSurface() {
return (
Custom Styled Surface
Content goes here
);
}
```
### Customizing the component classes
To customize the Surface component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.surface {
@apply rounded-2xl border border-border;
}
.surface--secondary {
@apply bg-gradient-to-br from-blue-50 to-purple-50;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Surface component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/surface.css)):
#### Base Classes
* `.surface` - Base surface container
#### Variant Classes
* `.surface--default` - Default surface variant (bg-surface)
* `.surface--secondary` - Secondary surface variant (bg-surface-secondary)
* `.surface--tertiary` - Tertiary surface variant (bg-surface-tertiary)
## API Reference
### Surface Props
| Prop | Type | Default | Description |
| ----------- | ---------------------------------------------------------- | ----------- | --------------------------------- |
| `variant` | ` "transparent" \| "default" \| "secondary" \| "tertiary"` | `"default"` | The visual variant of the surface |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The surface content |
## Context API
### SurfaceContext
Child components can access the Surface context to get the current variant:
```tsx
import { useContext } from 'react';
import { SurfaceContext } from '@darkcode-ui/react';
function MyComponent() {
const { variant } = useContext(SurfaceContext);
// variant will be "transparent" | "default" | "secondary" | "tertiary" | undefined
}
```
# Toolbar
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/toolbar
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(layout)/toolbar.mdx
> A container for interactive controls with arrow key navigation.
## Import
```tsx
import { Toolbar } from '@darkcode-ui/react';
```
### Usage
```tsx
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@darkcode-ui/react";
import {Bold, Copy, Italic, Scissors, Underline} from "@gravity-ui/icons";
export function Basic() {
return (
);
}
```
### Vertical
```tsx
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@darkcode-ui/react";
import {ArrowUturnCcwLeft, ArrowUturnCwRight, Bold, Italic, Underline} from "@gravity-ui/icons";
export function Vertical() {
return (
);
}
```
### With ButtonGroup
```tsx
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@darkcode-ui/react";
import {
ArrowUturnCcwLeft,
ArrowUturnCwRight,
Bold,
Italic,
TextAlignCenter,
TextAlignLeft,
TextAlignRight,
Underline,
} from "@gravity-ui/icons";
export function WithButtonGroup() {
return (
Undo
Redo
);
}
```
### Attached
```tsx
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@darkcode-ui/react";
import {Bold, Copy, Italic, Scissors, Underline} from "@gravity-ui/icons";
export function Attached() {
return (
);
}
```
## Related Components
* **ButtonGroup**: Group related buttons together
* **ToggleButtonGroup**: Group multiple toggle buttons into a unified control
* **Separator**: Visual divider between content
## Styling
### Passing Tailwind CSS classes
```tsx
import { Toolbar } from '@darkcode-ui/react';
function CustomToolbar() {
return (
{/* toolbar content */}
);
}
```
### Customizing the component classes
To customize the Toolbar component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.toolbar {
@apply gap-4 rounded-lg bg-surface p-3;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Toolbar component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/toolbar.css)):
* `.toolbar` - Base container
* `.toolbar--horizontal` - Horizontal orientation (default)
* `.toolbar--vertical` - Vertical orientation
* `.toolbar--attached` - Attached variant with surface background and full rounding
## API Reference
### Toolbar Props
Inherits from [React Aria Toolbar](https://react-spectrum.adobe.com/react-aria/Toolbar.html).
| Prop | Type | Default | Description |
| ----------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------- |
| `isAttached` | `boolean` | `false` | Whether the toolbar has a surface background with full rounding |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | The orientation of the toolbar |
| `aria-label` | `string` | - | An accessible label for the toolbar |
| `aria-labelledby` | `string` | - | The id of an element that labels the toolbar |
| `children` | `React.ReactNode \| (values: ToolbarRenderProps) => React.ReactNode` | - | Content or render prop |
| `className` | `string \| (values: ToolbarRenderProps) => string` | - | Additional CSS classes |
### ToolbarRenderProps
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ------------- | ---------------------------- | -------------------------------------- |
| `orientation` | `"horizontal" \| "vertical"` | The current orientation of the toolbar |
# Avatar
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/avatar
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(media)/avatar.mdx
> Display user profile images with customizable fallback content
## Import
```tsx
import { Avatar } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Avatar} from "@darkcode-ui/react";
export function Basic() {
return (
);
}
```
### Anatomy
Import the Avatar component and access all parts using dot notation.
```tsx
import { Avatar } from '@darkcode-ui/react';
export default () => (
)
```
### Sizes
```tsx
import {Avatar} from "@darkcode-ui/react";
export function Sizes() {
return (
);
}
```
### Colors
```tsx
import {Avatar} from "@darkcode-ui/react";
export function Colors() {
return (
);
}
```
### Variants
```tsx
import {Avatar, Separator} from "@darkcode-ui/react";
import {Person} from "@gravity-ui/icons";
export function Variants() {
const colors = ["accent", "default", "success", "warning", "danger"] as const;
const variants = [
{content: "AG", label: "letter", type: "letter"},
{content: "AG", label: "letter soft", type: "letter-soft"},
{content: , label: "icon", type: "icon"},
{content: , label: "icon soft", type: "icon-soft"},
{
content: [
"https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-3.jpg",
"https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-4.jpg",
"https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-5.jpg",
"https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-8.jpg",
"https://darkcode-ui-assets.darkcode.dev/avatars/placeholder-16.jpg",
],
label: "img",
type: "img",
},
] as const;
return (
{/* Color labels header */}
{colors.map((color) => (
{color}
))}
{/* Variant rows */}
{variants.map((variant) => (
{variant.label}
{colors.map((color, colorIndex) => (
{variant.type === "img" ? (
<>
{color.charAt(0).toUpperCase()}
>
) : (
{variant.content}
)}
))}
))}
);
}
```
### Fallback Content
```tsx
import {Avatar} from "@darkcode-ui/react";
import {Person} from "@gravity-ui/icons";
export function Fallback() {
return (
{/* Text fallback */}
JD
{/* Icon fallback */}
{/* Fallback with delay */}
NA
{/* Custom styled fallback */}
GB
);
}
```
### Avatar Group
```tsx
import {Avatar} from "@darkcode-ui/react";
const users = [
{
id: 1,
image: "https://darkcode-ui-assets.darkcode.dev/avatars/blue.jpg",
name: "John Doe",
},
{
id: 2,
image: "https://darkcode-ui-assets.darkcode.dev/avatars/green.jpg",
name: "Kate Wilson",
},
{
id: 3,
image: "https://darkcode-ui-assets.darkcode.dev/avatars/purple.jpg",
name: "Emily Chen",
},
{
id: 4,
image: "https://darkcode-ui-assets.darkcode.dev/avatars/orange.jpg",
name: "Michael Brown",
},
{
id: 5,
image: "https://darkcode-ui-assets.darkcode.dev/avatars/red.jpg",
name: "Olivia Davis",
},
];
export function Group() {
return (
{/* Basic avatar group */}
{users.slice(0, 4).map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
{/* Avatar group with counter */}
{users.slice(0, 3).map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
+{users.length - 3}
);
}
```
### Custom Styles
```tsx
import {Avatar} from "@darkcode-ui/react";
export function CustomStyles() {
return (
{/* Custom size with Tailwind classes */}
XL
{/* Square avatar */}
SQ
{/* Gradient border */}
{/* Status indicator */}
);
}
```
## Related Components
* **Separator**: Visual divider between content
* **Badge**: Small indicator positioned relative to another element
## Styling
### Passing Tailwind CSS classes
```tsx
import { Avatar } from '@darkcode-ui/react';
function CustomAvatar() {
return (
XL
);
}
```
### Customizing the component classes
To customize the Avatar component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.avatar {
@apply size-16 border-2 border-primary;
}
.avatar__fallback {
@apply bg-gradient-to-br from-purple-500 to-pink-500;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Avatar component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/avatar.css)):
#### Base Classes
* `.avatar` - Base container with default size (size-10)
* `.avatar__image` - Image element with aspect-square sizing
* `.avatar__fallback` - Fallback container with centered content
#### Size Modifiers
* `.avatar--sm` - Small avatar (size-8)
* `.avatar--md` - Medium avatar (default, no additional styles)
* `.avatar--lg` - Large avatar (size-12)
#### Variant Modifiers
* `.avatar--soft` - Soft variant with lighter background
#### Color Modifiers
* `.avatar__fallback--default` - Default text color
* `.avatar__fallback--accent` - Accent text color
* `.avatar__fallback--success` - Success text color
* `.avatar__fallback--warning` - Warning text color
* `.avatar__fallback--danger` - Danger text color
## API Reference
### Avatar Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | ----------- | ---------------------- |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Avatar size |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | `'default'` | Fallback color theme |
| `variant` | `'default' \| 'soft'` | `'default'` | Visual style variant |
| `className` | `string` | - | Additional CSS classes |
### Avatar.Image Props
| Prop | Type | Default | Description |
| ------------- | --------------------------------------------------- | ------- | -------------------------------------------------- |
| `src` | `string` | - | Image source URL |
| `srcSet` | `string` | - | The image `srcset` attribute for responsive images |
| `sizes` | `string` | - | The image `sizes` attribute for responsive images |
| `alt` | `string` | - | Alternative text for the image |
| `onLoad` | `(event: SyntheticEvent) => void` | - | Callback when the image loads successfully |
| `onError` | `(event: SyntheticEvent) => void` | - | Callback when there's an error loading the image |
| `crossOrigin` | `'anonymous' \| 'use-credentials'` | - | CORS setting for the image request |
| `loading` | `'eager' \| 'lazy'` | - | Native lazy loading attribute |
| `className` | `string` | - | Additional CSS classes |
### Avatar.Fallback Props
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | ------- | ---------------------------------------------- |
| `delayMs` | `number` | - | Delay before showing fallback (prevents flash) |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | - | Override color from parent |
| `className` | `string` | - | Additional CSS classes |
# Accordion
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/accordion
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(navigation)/accordion.mdx
> A collapsible content panel for organizing information in a compact space
## Import
```tsx
import { Accordion } from '@darkcode-ui/react';
```
### Usage
```tsx
import {Accordion} from "@darkcode-ui/react";
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
const items = [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: ,
title: "How do I place an order?",
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: ,
title: "Can I modify or cancel my order?",
},
{
content: "We accept all major credit cards, including Visa, Mastercard, and American Express.",
icon: ,
title: "What payment methods do you accept?",
},
{
content:
"Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.",
icon: ,
title: "How much does shipping cost?",
},
{
content:
"Yes, we ship to most countries. Please check our shipping rates and policies for more information.",
icon: ,
title: "Do you ship internationally?",
},
{
content:
"If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.",
icon: ,
title: "How do I request a refund?",
},
];
export function Basic() {
return (
{items.map((item, index) => (
{item.icon ? (
{item.icon}
) : null}
{item.title}
{item.content}
))}
);
}
```
### Anatomy
Import the Accordion component and access all parts using dot notation.
```tsx
import { Accordion } from '@darkcode-ui/react';
export default () => (
)
```
### Surface
```tsx
import {Accordion} from "@darkcode-ui/react";
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
const items = [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: ,
title: "How do I place an order?",
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: ,
title: "Can I modify or cancel my order?",
},
{
content: "We accept all major credit cards, including Visa, Mastercard, and American Express.",
icon: ,
title: "What payment methods do you accept?",
},
{
content:
"Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.",
icon: ,
title: "How much does shipping cost?",
},
{
content:
"Yes, we ship to most countries. Please check our shipping rates and policies for more information.",
icon: ,
title: "Do you ship internationally?",
},
{
content:
"If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.",
icon: ,
title: "How do I request a refund?",
},
];
export function Surface() {
return (
{items.map((item, index) => (
{item.icon ? (
{item.icon}
) : null}
{item.title}
{item.content}
))}
);
}
```
### Multiple Expanded
```tsx
import {Accordion} from "@darkcode-ui/react";
export function Multiple() {
return (
Getting Started
Learn the basics of DarkCode UI and how to integrate it into your React project. This
section covers installation, setup, and your first component.
Core Concepts
Understand the fundamental concepts behind DarkCode UI, including the compound component
pattern, styling with Tailwind CSS, and accessibility features.
Advanced Usage
Explore advanced features like custom variants, theme customization, and integration
with other libraries in your React ecosystem.
Best Practices
Follow our recommended best practices for building performant, accessible, and
maintainable applications with DarkCode UI components.
);
}
```
### Controlled
```tsx
"use client";
import {Accordion, Button, useDisclosureGroupNavigation} from "@darkcode-ui/react";
import {ChevronDown, ChevronUp} from "@gravity-ui/icons";
import React from "react";
const items = [
{
content:
"Learn the basics of DarkCode UI and how to integrate it into your React project. This section covers installation, setup, and your first component.",
id: "getting-started",
title: "Getting Started",
},
{
content:
"Understand the fundamental concepts behind DarkCode UI, including the compound component pattern, styling with Tailwind CSS, and accessibility features.",
id: "core-concepts",
title: "Core Concepts",
},
{
content:
"Explore advanced features like custom variants, theme customization, and integration with other libraries in your React ecosystem.",
id: "advanced-usage",
title: "Advanced Usage",
},
];
export function Controlled() {
const [expandedKeys, setExpandedKeys] = React.useState(
new Set(["getting-started"]),
);
const itemIds = items.map((item) => item.id);
const {isNextDisabled, isPrevDisabled, onNext, onPrevious} = useDisclosureGroupNavigation({
expandedKeys,
itemIds,
onExpandedChange: setExpandedKeys,
});
return (
Expanded: {[...expandedKeys].join(", ") || "none"}
{items.map((item) => (
{item.title}
{item.content}
))}
);
}
```
### Custom Indicator
```tsx
"use client";
import type {Key} from "@darkcode-ui/react";
import {Accordion} from "@darkcode-ui/react";
import {ChevronsDown, CircleChevronDown, Minus, Plus} from "@gravity-ui/icons";
import React from "react";
export function CustomIndicator() {
const [expandedKeys, setExpandedKeys] = React.useState>(new Set([""]));
return (
Using Plus/Minus Icon
{expandedKeys.has("1") ? : }
This accordion uses a plus icon that transforms when expanded. The icon automatically
rotates 45 degrees to form an X.
Using Caret Icon
This item uses a caret icon for the indicator. The rotation animation is applied
automatically.
Using Arrow Icon
This item uses an arrow icon. Any icon you pass will receive the rotation animation when
the item expands.
);
}
```
### Disabled State
```tsx
import {Accordion} from "@darkcode-ui/react";
export function Disabled() {
return (
Entire accordion disabled
Disabled Item 1
This content cannot be accessed when the accordion is disabled.
Disabled Item 2
This content cannot be accessed when the accordion is disabled.
Individual items disabled
Active Item
This item is active and can be toggled normally.
Disabled Item
This content cannot be accessed when the item is disabled.
Another Active Item
This item is also active and can be toggled.
);
}
```
### FAQ Layout
```tsx
import {Accordion} from "@darkcode-ui/react";
import {ChevronDown} from "@gravity-ui/icons";
export function FAQ() {
const categories = [
{
items: [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
title: "How do I place an order?",
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
title: "Can I modify or cancel my order?",
},
],
title: "General",
},
{
items: [
{
content:
"You can purchase a license directly from our website. Select the license type that fits your needs and proceed to checkout.",
title: "How do I purchase a license?",
},
{
content:
"A standard license is for personal use or small projects, while a pro license includes commercial use rights and priority support.",
title: "What is the difference between a standard and a pro license?",
},
],
title: "Licensing",
},
{
items: [
{
content:
"You can reach our support team through the contact form on our website, or email us directly at support@example.com.",
title: "How do I get support?",
},
],
title: "Support",
},
];
return (
Frequently Asked Questions
Everything you need to know about licensing and usage.
{categories.map((category) => (
{category.title}
{category.items.map((item, index) => (
{item.title}
{item.content}
))}
))}
);
}
```
### Custom Styles
```tsx
import {Accordion, cn} from "@darkcode-ui/react";
import {ChevronDown} from "@gravity-ui/icons";
const items = [
{
content: "Stay informed about your account activity with real-time notifications. ",
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/bell-small.png",
subtitle: "Receive account activity updates",
title: "Set Up Notifications",
},
{
content: "Enhance your browsing experience by installing our official browser extension",
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/compass-small.png",
subtitle: "Connect you browser to your account",
title: "Set up Browser Extension",
},
{
content:
"Begin your journey into the world of digital collectibles by creating your first NFT. ",
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/mint-collective-small.png",
subtitle: "Create your first collectible",
title: "Mint Collectible",
},
];
export function CustomStyles() {
return (
{items.map((item, index) => (
{item.iconUrl ? (
) : null}
{item.title}
{item.subtitle}
{item.content}
))}
);
}
```
### Without Separator
```tsx
import {Accordion} from "@darkcode-ui/react";
import {ChevronDown, CreditCard, Receipt, ShoppingBag} from "@gravity-ui/icons";
const items = [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: ,
title: "How do I place an order?",
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: ,
title: "Can I modify or cancel my order?",
},
{
content: "We accept all major credit cards, including Visa, Mastercard, and American Express.",
icon: ,
title: "What payment methods do you accept?",
},
];
export function WithoutSeparator() {
return (
{items.map((item, index) => (
{item.icon ? (
{item.icon}
) : null}
{item.title}
{item.content}
))}
);
}
```
### Custom Render Function
```tsx
"use client";
import {Accordion} from "@darkcode-ui/react";
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
const items = [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: ,
title: "How do I place an order?",
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: ,
title: "Can I modify or cancel my order?",
},
{
content: "We accept all major credit cards, including Visa, Mastercard, and American Express.",
icon: ,
title: "What payment methods do you accept?",
},
{
content:
"Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.",
icon: ,
title: "How much does shipping cost?",
},
{
content:
"Yes, we ship to most countries. Please check our shipping rates and policies for more information.",
icon: ,
title: "Do you ship internationally?",
},
{
content:
"If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.",
icon: ,
title: "How do I request a refund?",
},
];
export function CustomRenderFunction() {
return (
}
>
{items.map((item, index) => (
}>
}>
}>
{item.icon ? (
{item.icon}
) : null}
{item.title}
}>
{item.content}
))}
);
}
```
## Related Components
* **DisclosureGroup**: Group of collapsible panels
* **Disclosure**: Single collapsible content section
## Styling
### Passing Tailwind CSS classes
```tsx
"use client";
import { Accordion, cn } from "@darkcode-ui/react";
import {Icon} from "@iconify/react";
const items = [
{
content:
"Stay informed about your account activity with real-time notifications. You'll receive instant alerts for important events like transactions, new messages, security updates, and system announcements. ",
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/bell-small.png",
title: "Set Up Notifications",
subtitle: "Receive account activity updates",
},
{
content:
"Enhance your browsing experience by installing our official browser extension. The extension provides seamless integration with your account, allowing you to receive notifications directly in your browser, quickly access your dashboard, and interact with web3 applications securely. Compatible with Chrome, Firefox, Edge, and Brave browsers.",
iconUrl: "https://darkcode-ui-assets.darkcode.dev/docs/3dicons/compass-small.png",
title: "Set up Browser Extension",
subtitle: "Connect you browser to your account",
},
{
content:
"Begin your journey into the world of digital collectibles by creating your first NFT. Our intuitive minting process guides you through uploading your artwork, setting metadata, choosing royalty percentages, and deploying to the blockchain. Whether you're an artist, creator, or collector, you'll find all the tools you need to bring your digital assets to life. Your collectibles are stored on IPFS for permanent decentralized storage.",
iconUrl:
"https://darkcode-ui-assets.darkcode.dev/docs/3dicons/mint-collective-small.png",
title: "Mint Collectible",
subtitle: "Create your first collectible",
},
];
export function CustomStyles() {
return (
{items.map((item, index) => (
{item.iconUrl ? (
) : null}
{item.title}
{item.subtitle}
{item.content}
))}
);
}
```
### Customizing the component classes
To customize the Accordion component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.accordion {
@apply rounded-xl bg-gray-50;
}
.accordion__trigger {
@apply font-semibold text-lg;
}
.accordion--outline {
@apply shadow-lg border-2;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Accordion component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/accordion.css)):
#### Base Classes
* `.accordion` - Base accordion container
* `.accordion__body` - Content body container
* `.accordion__heading` - Heading wrapper
* `.accordion__indicator` - Expand/collapse indicator icon
* `.accordion__item` - Individual accordion item
* `.accordion__panel` - Collapsible panel container
* `.accordion__trigger` - Clickable trigger button
#### Variant Classes
* `.accordion--outline` - Outline variant with border and background
#### State Classes
* `.accordion__trigger[aria-expanded="true"]` - Expanded state
* `.accordion__panel[aria-hidden="false"]` - Panel visible state
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
* **Hover**: `:hover` or `[data-hovered="true"]` on trigger
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on trigger
* **Disabled**: `:disabled` or `[aria-disabled="true"]` on trigger
* **Expanded**: `[aria-expanded="true"]` on trigger
## API Reference
### Accordion Props
| Prop | Type | Default | Description |
| ------------------------ | ---------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------- |
| `allowsMultipleExpanded` | `boolean` | `false` | Whether multiple items can be expanded at once |
| `defaultExpandedKeys` | `Iterable` | - | The initial expanded keys |
| `expandedKeys` | `Iterable` | - | The controlled expanded keys |
| `onExpandedChange` | `(keys: Set) => void` | - | Handler called when expanded keys change |
| `isDisabled` | `boolean` | `false` | Whether the entire accordion is disabled |
| `variant` | `"default" \| "surface"` | `"default"` | The visual variant of the accordion |
| `hideSeparator` | `boolean` | `false` | Hide separator lines between accordion items |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The accordion items |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Accordion.Item Props
| Prop | Type | Default | Description |
| ------------------ | -------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `id` | `Key` | - | Unique identifier for the item |
| `isDisabled` | `boolean` | `false` | Whether this item is disabled |
| `defaultExpanded` | `boolean` | `false` | Whether item is initially expanded |
| `isExpanded` | `boolean` | - | Controlled expanded state |
| `onExpandedChange` | `(isExpanded: boolean) => void` | - | Handler for expanded state changes |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The item content |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Accordion.Trigger Props
| Prop | Type | Default | Description |
| ------------ | -------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Trigger content or render function |
| `onPress` | `() => void` | - | Additional press handler |
| `isDisabled` | `boolean` | - | Whether trigger is disabled |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Accordion.Panel Props
| Prop | Type | Default | Description |
| ----------- | --------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Panel content |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Accordion.Indicator Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator icon |
### Accordion.Body Props
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Body content |
# Breadcrumbs
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/breadcrumbs
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(navigation)/breadcrumbs.mdx
> Navigation breadcrumbs showing the current page's location within a hierarchy
## Import
```tsx
import { Breadcrumbs } from '@darkcode-ui/react';
```
### Usage
```tsx
"use client";
import {Breadcrumbs} from "@darkcode-ui/react";
export default function BreadcrumbsBasic() {
return (
Home
Products
Electronics
Laptop
);
}
```
### Anatomy
Import the Breadcrumbs component and access all parts using dot notation.
```tsx
import { Breadcrumbs } from '@darkcode-ui/react';
export default () => (
Home
Category
Current Page
)
```
### Navigation Levels
```tsx
"use client";
import {Breadcrumbs} from "@darkcode-ui/react";
export default function BreadcrumbsLevel2() {
return (
Home
Current Page
);
}
```
```tsx
"use client";
import {Breadcrumbs} from "@darkcode-ui/react";
export default function BreadcrumbsLevel3() {
return (
Home
Category
Current Page
);
}
```
### Custom Separator
```tsx
"use client";
import {Breadcrumbs} from "@darkcode-ui/react";
export default function BreadcrumbsCustomSeparator() {
return (
}
>
Home
Products
Electronics
Laptop
);
}
```
### Disabled State
```tsx
"use client";
import {Breadcrumbs} from "@darkcode-ui/react";
export default function BreadcrumbsDisabled() {
return (
Home
Products
Electronics
Laptop
);
}
```
### Custom Render Function
```tsx
"use client";
import {Breadcrumbs} from "@darkcode-ui/react";
export function CustomRenderFunction() {
return (
}>
}>
Home
}>
Products
}>
Electronics
}>
Laptop
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import { Breadcrumbs } from '@darkcode-ui/react';
function CustomBreadcrumbs() {
return (
Home
Current
);
}
```
### Customizing the component classes
To customize the Breadcrumbs component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.breadcrumbs {
@apply gap-4 text-lg;
}
.breadcrumbs__link {
@apply font-semibold;
}
.breadcrumbs__separator {
@apply text-blue-500;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Breadcrumbs component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/breadcrumbs.css)):
#### Base Classes
* `.breadcrumbs` - Base breadcrumbs container
* `.breadcrumbs__item` - Individual breadcrumb item wrapper
* `.breadcrumbs__link` - Breadcrumb link element
* `.breadcrumbs__separator` - Separator icon between items
#### State Classes
* `.breadcrumbs__link[data-current="true"]` - Current page indicator (not a link)
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
* **Current**: `[data-current="true"]` on link (indicates current page)
* **Hover**: Link elements support standard hover states
* **Disabled**: `isDisabled` prop disables all links
## API Reference
### Breadcrumbs Props
| Prop | Type | Default | Description |
| ------------ | ----------------------------------------------------------------- | ------------------ | --------------------------------------------------------------- |
| `separator` | `ReactNode` | chevron-right icon | Custom separator between breadcrumb items |
| `isDisabled` | `boolean` | `false` | Whether all breadcrumb links are disabled |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The breadcrumb items |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function |
### Breadcrumbs.Item Props
| Prop | Type | Default | Description |
| ----------- | ----------------------------------------------------------------------------- | ------- | --------------------------------------------------------------- |
| `href` | `string` | - | The URL to link to (omit for current page) |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Item content or render function |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function |
## Accessibility
Breadcrumbs uses React Aria Components' Breadcrumbs primitive, which provides:
* Proper ARIA attributes for navigation landmarks
* Current page indication via `aria-current="page"`
* Keyboard navigation support
* Screen reader announcements for navigation context
The last breadcrumb item (without `href`) automatically becomes the current page indicator.
# Command
**Category**: react
**URL**: https://ui.darkcode.dev/en/docs/react/components/command
**Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(navigation)/command.mdx
> A command palette with fuzzy search, keyboard navigation, and grouped actions for quick access.
## Import
```tsx
import { Command } from '@darkcode-ui/react';
```
## Anatomy
Import the Command component and access all parts using dot notation. `Command` is the root
provider — it wires up the overlay trigger, so place a trigger element (e.g. a `Button`) and the
`Command.Backdrop` inside it.
```tsx
```
### Usage
The palette opens from a trigger and filters its items as you type. `Command.Dialog` wraps an
internal React Aria `Autocomplete`, so arrow keys move focus through the filtered list and `Enter`
activates the focused item. Give each `Command.Item` a `textValue` so it can be matched while
searching.
```tsx
"use client";
import {Button, Command, Kbd, useOverlayState} from "@darkcode-ui/react";
import {CreditCard, FolderOpen, Magnifier, Person, Sparkles, SquarePlus} from "@gravity-ui/icons";
import {useEffect} from "react";
export function CommandDefault() {
const state = useOverlayState();
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
state.toggle();
}
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [state]);
return (
Open Command
⌘K
state.close()}>
New File
⌘N
New Folder
⌘⇧N
Search Files
⌘P
Profile
⌘,
Appearance
Billing
↑
↓
to navigate
↵
to select
esc
to close
);
}
```
### Opening with a shortcut
Drive the palette with [`useOverlayState`](/react/hooks/use-overlay-state) and pass it to
`Command` via the `state` prop to bind a global shortcut such as ⌘K . Call `state.close()`
from `Command.List`'s `onAction` to dismiss the palette after a selection.
```tsx
import {Button, Command, useOverlayState} from '@darkcode-ui/react';
import {useEffect} from 'react';
function CommandMenu() {
const state = useOverlayState();
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
event.preventDefault();
state.toggle();
}
};
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, [state]);
return (
Open
state.close()}>
Profile
);
}
```
### Clean
A pared-back palette with a single flat list and no header or footer.
```tsx
"use client";
import {Button, Command, useOverlayState} from "@darkcode-ui/react";
export function CommandClean() {
const state = useOverlayState();
return (
Open Command
state.close()}>
Dashboard
Projects
Team
Reports
Settings
);
}
```
### Dev Toolbar
Add a `Command.Header` above the input to host a title, breadcrumbs, or tabs.
```tsx
"use client";
import {Button, Chip, Command, Kbd, useOverlayState} from "@darkcode-ui/react";
import {ArrowUturnCwRight, CircleDashed, CircleInfo, Gear, TrashBin} from "@gravity-ui/icons";
export function CommandDevToolbar() {
const state = useOverlayState();
return (
Open Dev Toolbar
Developer Tools
state.close()}>
Toggle Inspector
beta
Show Performance Overlay
Clear Cache
Rebuild Project
⌘B
Restart Server
);
}
```
### Launcher
An application launcher grouping recent items and apps inside a larger dialog.
```tsx
"use client";
import {Button, Command, useOverlayState} from "@darkcode-ui/react";
import {Bell, Calendar, Envelope, Globe, HardDrive, SquareArticle} from "@gravity-ui/icons";
export function CommandLauncher() {
const state = useOverlayState();
return (
Open Launcher
state.close()}>
Calendar
Mail
Notes
Browser
Files
Reminders
);
}
```
### Minimal
Just an input and a list — omit the prefix, clear button, groups, and footer.
```tsx
"use client";
import {Button, Command, useOverlayState} from "@darkcode-ui/react";
export function CommandMinimal() {
const state = useOverlayState();
return (
Open Command
state.close()}>
Copy
Cut
Paste
Duplicate
Delete
);
}
```
### Split View
Combine a `Command.Header` with switchable lists to scope results to a category.
```tsx
"use client";
import {Button, Command, useOverlayState} from "@darkcode-ui/react";
import {Bookmark, Envelope, Gear, House, Person, SquarePlus} from "@gravity-ui/icons";
import {useState} from "react";
export function CommandSplitView() {
const state = useOverlayState();
const [category, setCategory] = useState<"actions" | "navigation">("actions");
return (
Open Command
{(["actions", "navigation"] as const).map((value) => (
setCategory(value)}
>
{value}
))}
{category === "actions" ? (
state.close()}>
Create Issue
Assign to me
Add Label
) : (
state.close()}>
Go to Dashboard
Go to Inbox
Go to Settings
)}
);
}
```
### Multiple Search Terms
Pass a custom `filter` to `Command.Dialog` to match every whitespace-separated term independently,
so `dark settings` matches `Toggle Dark Mode Settings`.
```tsx
"use client";
import {Button, Command, useOverlayState} from "@darkcode-ui/react";
import {ArrowUpFromLine, Person, Persons, Sparkles} from "@gravity-ui/icons";
import {useCallback} from "react";
export function CommandMultipleSearchTerms() {
const state = useOverlayState();
// Matches when every whitespace-separated term is contained in the item text,
// so "dark settings" matches "Toggle Dark Mode Settings".
const filter = useCallback((textValue: string, inputValue: string) => {
const haystack = textValue.toLowerCase();
return inputValue
.toLowerCase()
.split(/\s+/)
.filter(Boolean)
.every((term) => haystack.includes(term));
}, []);
return (
Open Command
state.close()}>
Toggle Dark Mode Settings
Open Account Settings
Export Data as CSV
Invite Team Member
);
}
```
### Sizes
Set `size` on `Command.Container` to `sm`, `md` (default), or `lg`.
```tsx
"use client";
import type {CommandContainerProps} from "@darkcode-ui/react";
import {Button, Command, useOverlayState} from "@darkcode-ui/react";
function SizedCommand({size}: {size: CommandContainerProps["size"]}) {
const state = useOverlayState();
return (
{size}
state.close()}>
Profile
Billing
Notifications
Security
);
}
export function CommandSizes() {
return (
);
}
```
### Backdrop Variants
Set `variant` on `Command.Backdrop` to `opaque` (default), `blur`, or `transparent`.
```tsx
"use client";
import type {CommandBackdropProps} from "@darkcode-ui/react";
import {Button, Command, useOverlayState} from "@darkcode-ui/react";
function BackdropCommand({variant}: {variant: CommandBackdropProps["variant"]}) {
const state = useOverlayState();
return (
{variant}
state.close()}>
Home
Explore
Library
);
}
export function CommandBackdropVariants() {
return (
);
}
```
## Styling
### Passing Tailwind CSS classes
```tsx
import {Command} from '@darkcode-ui/react';
{/* ... */}
```
### Customizing the component classes
To customize the Command component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.command__dialog {
@apply rounded-3xl;
}
.command__item {
@apply rounded-lg;
}
.command__group-heading {
@apply uppercase tracking-wide;
}
}
```
DarkCode UI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Command component uses these CSS classes ([View source styles](https://github.com/DarkCode-Developers/darkcode-ui/blob/main/packages/styles/components/command.css)):
#### Base & Variant Classes
* `.command__backdrop` — Fixed fullscreen overlay behind the palette, with enter/exit animations.
* `.command__backdrop--transparent` — Fully transparent backdrop.
* `.command__backdrop--opaque` — Dark semi-transparent backdrop.
* `.command__backdrop--blur` — Dark backdrop with `backdrop-blur-md`.
#### Size Modifier Classes
* `.command__dialog--sm` — Small dialog (`max-w-sm`, max-height `300px`).
* `.command__dialog--md` — Medium dialog (`max-w-lg`, max-height `356px`). Default.
* `.command__dialog--lg` — Large dialog (`max-w-xl`, max-height `440px`).
#### Element Classes
* `.command__container` — Positioning wrapper that centers the dialog near the top of the viewport with slide + zoom animations.
* `.command__dialog` — The command palette box (`bg-overlay`, `shadow-overlay`).
* `.command__header` — Content area above the input.
* `.command__input-group` — Search field row holding prefix, input, and suffix.
* `.command__input-group-prefix` — Leading content (e.g. a search icon).
* `.command__input-group-input` — The text input.
* `.command__input-group-suffix` — Trailing content.
* `.command__input-group-clear-button` — Clear button; hidden while the input is empty.
* `.command__list` — Scrollable command list (`overflow-y-auto`, `overscroll-contain`).
* `.command__group` — Section grouping items.
* `.command__group-heading` — Section label.
* `.command__item` — Individual command entry.
* `.command__separator` — Horizontal divider between groups.
* `.command__footer` — Bottom bar with hints.
* `.command__empty` — Empty state shown when no results match.
#### Interactive States
* `[data-entering]` / `[data-exiting]` on `.command__backdrop` and `.command__container` — enter/exit animations.
* `[data-hovered]` / `[data-focused]` on `.command__item` — applies `bg-default`.
* `[data-pressed]` on `.command__item` — applies `bg-default-hover`.
* `[data-disabled]` on `.command__item` — reduced opacity, default cursor.
* `[data-empty]` on `.command__input-group` — hides the clear button.
* `motion-reduce:animate-none` on all animated elements.
## API Reference
### Command
The root provider. Sets up the component context and overlay trigger.
| Prop | Type | Default | Description |
| ---------- | ----------------------- | ------- | ------------------------------------------------------------------------ |
| `state` | `UseOverlayStateReturn` | — | Controlled overlay state from `useOverlayState` (e.g. for ⌘K shortcuts). |
| `children` | `ReactNode` | — | A trigger element and the `Command.Backdrop`. |
Also supports all React Aria [DialogTrigger](https://react-spectrum.adobe.com/react-aria/Dialog.html) props.
### Command.Backdrop
The fullscreen overlay. Must wrap `Command.Container`.
| Prop | Type | Default | Description |
| --------------- | ------------------------------------- | ---------- | ------------------------------------------------- |
| `variant` | `'opaque' \| 'blur' \| 'transparent'` | `'opaque'` | Backdrop visual style. |
| `isDismissable` | `boolean` | `true` | Whether clicking the backdrop closes the palette. |
Also supports all React Aria [ModalOverlay](https://react-spectrum.adobe.com/react-aria/Modal.html) props.
### Command.Container
Positioning wrapper that centers the dialog. Must be placed inside `Command.Backdrop`.
| Prop | Type | Default | Description |
| ------ | ---------------------- | ------- | --------------------------- |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the command dialog. |
Also supports all React Aria [Modal](https://react-spectrum.adobe.com/react-aria/Modal.html) props.
### Command.Dialog
The command palette box. Wraps an internal `Autocomplete` for filtering.
| Prop | Type | Default | Description |
| ------------------- | ---------------------------------------------------- | ------------------------- | ------------------------------------------ |
| `defaultInputValue` | `string` | — | Default search input value (uncontrolled). |
| `inputValue` | `string` | — | Controlled search input value. |
| `onInputChange` | `(value: string) => void` | — | Callback when the search input changes. |
| `filter` | `(textValue: string, inputValue: string) => boolean` | Case-insensitive contains | Custom filter function. |
Also supports all React Aria [Dialog](https://react-spectrum.adobe.com/react-aria/Dialog.html) props.
### Command.Header
Content area above the input (e.g. breadcrumbs or tabs). Renders a plain `