`)
* `.table__cell` - Data cell (``)
* `.table__footer` - Footer container (outside table)
#### Advanced Classes \[!toc]
* `.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 \[!toc]
* `.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
| 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
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Table.Content element |
### Table.Content
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
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
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
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
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
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.SortableColumnHeader
Renders a sortable column label with an ascending/descending indicator. Use it inside a `Table.Column` render-prop callback and forward the `sortDirection` value.
| Prop | Type | Default | Description |
| --------------- | ----------------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `sortDirection` | `"ascending" \| "descending"` | - | Current sort direction. Pass through from the `Table.Column` render prop. |
| `showIndicator` | `boolean` | `true` | Whether to render the sort indicator icon when a direction is set. |
| `indicator` | `React.ReactNode` | - | Custom indicator element. Overrides the default chevron and receives a `data-direction` attribute. |
| `className` | `string` | - | Additional CSS classes for the wrapper. |
| `children` | `React.ReactNode` | - | Column label content |
### Table.Footer
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | --------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Footer content (e.g., pagination) |
### Table.ColumnResizer
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
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
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
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ----------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Loading indicator content (e.g., Spinner) |
### Table.Collection
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. |
## Related Components
## Related Components
* **Pagination**: Page navigation with composable page links and controls
* **Checkbox**: Binary choice input control
* **Chip**: Compact elements for tags and filters
# Calendar
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/calendar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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
## Usage
```tsx
import { Calendar } from '@heroui/react';
```
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function Basic() {
return (
{(day) => {day} }
{(date) => }
);
}
```
## Anatomy
```tsx
import {Calendar} from '@heroui/react';
export default () => (
{(day) => {day} }
{(date) => }
)
```
## Examples
### Disabled
```tsx
"use client";
import {Calendar, Description} from "@heroui/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 "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function ReadOnly() {
return (
{(day) => {day} }
{(date) => }
Calendar is read-only
);
}
```
### Default Value
```tsx
"use client";
import {Calendar} from "@heroui/react";
import {parseDate} from "@internationalized/date";
export function DefaultValue() {
return (
{(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 "@heroui/react";
export function YearPicker() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Controlled
Use controlled `value` and `focusedValue` for external state coordination and custom shortcuts.
```tsx
"use client";
import type {CalendarDate} from "@internationalized/date";
import {Button, ButtonGroup, Calendar, Description} from "@heroui/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
);
}
```
### Min and Max Dates
```tsx
"use client";
import {Calendar, Description} from "@heroui/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 "@heroui/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
);
}
```
### Weeks in Month
Set `weeksInMonth` to a fixed value (for example, `6`) to keep the grid height stable when navigating between months. Use with care in non-Gregorian locales, similar to `firstDayOfWeek`.
```tsx
"use client";
import {Calendar, Description} from "@heroui/react";
export function WeeksInMonth() {
return (
{(day) => {day} }
{(date) => }
Always shows 6 weeks per month to avoid layout shift when navigating
);
}
```
### Week View
Set `visibleDuration={{ weeks: n }}` to show one or more weeks at a time. Navigation advances by the visible week range. Use `pageBehavior="single"` to move one week at a time when showing multiple weeks.
```tsx
"use client";
import {Calendar, Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
const weekOptions = [
{id: "1", name: "1 week"},
{id: "2", name: "2 weeks"},
{id: "3", name: "3 weeks"},
{id: "4", name: "4 weeks"},
{id: "5", name: "5 weeks"},
{id: "6", name: "6 weeks"},
{id: "8", name: "8 weeks"},
] as const;
export function WeekView() {
const [weeks, setWeeks] = useState(1);
return (
value && setWeeks(Number(value))}
>
Visible weeks
{weekOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### Day View
Set `visibleDuration={{ days: n }}` to show a rolling window of consecutive days. Navigation advances by the visible day range. Use `pageBehavior="single"` to move one day at a time when showing multiple days.
```tsx
"use client";
import {Calendar, Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
const dayOptions = [
{id: "1", name: "1 day"},
{id: "5", name: "5 days"},
{id: "7", name: "7 days"},
{id: "8", name: "8 days"},
{id: "10", name: "10 days"},
{id: "14", name: "14 days"},
{id: "21", name: "21 days"},
] as const;
export function DayView() {
const [days, setDays] = useState(5);
return (
value && setDays(Number(value))}
>
Visible days
{dayOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### Multiple Selection
Set `selectionMode="multiple"` to let users select several dates. `value`, `defaultValue`, and `onChange` use an array of dates.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar, Description} from "@heroui/react";
import {useState} from "react";
export function MultipleSelection() {
const [value, setValue] = useState([]);
return (
{(day) => {day} }
{(date) => }
{value?.length ? `${value.length} date(s) selected` : "Select multiple dates"}
);
}
```
### 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 "@heroui/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 "@heroui/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)) && (
)}
>
)}
)}
);
}
```
### Custom Navigation Icons
Pass children to `Calendar.NavButton` to replace the default chevron icons.
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function CustomIcons() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### Multiple Months
Render multiple grids with `visibleDuration` and `offset` for booking and planning experiences.
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function MultipleMonths() {
return (
{(day) => {day} }
{(date) => }
{(day) => {day} }
{(date) => }
);
}
```
### Real-World Example
```tsx
"use client";
import type {CalendarDate, DateValue} from "@internationalized/date";
import {Button, Calendar} from "@heroui/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}
);
}
```
### 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 "@heroui/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.
## Customization
### Tailwind CSS
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function CustomStyles() {
return (
{(day) => (
{day}
)}
{(date) => (
)}
);
}
```
### Global CSS
```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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### 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
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 or many dates can be selected. |
| `value` | `DateValue \| null` or `DateValue[] \| null` | - | Controlled selected date(s). Use an array when `selectionMode` is `multiple`. |
| `defaultValue` | `DateValue \| null` or `DateValue[] \| null` | - | Initial selected date(s) (uncontrolled). |
| `onChange` | `(value: DateValue \| null)` or `(value: DateValue[] \| null) => 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. |
| `weeksInMonth` | `number` | - | The number of weeks in a month. This overrides the default set by the locale. |
| `isDateUnavailable` | `(date: DateValue) => boolean` | - | Marks dates as unavailable. |
| `firstDayOfWeek` | `'sun' \| 'mon' \| 'tue' \| 'wed' \| 'thu' \| 'fri' \| 'sat'` | - | Overrides the locale default for the first day of the week. |
| `pageBehavior` | `'visible' \| 'single'` | `'visible'` | Whether paging advances by the visible duration or one unit at a time. |
| `selectionAlignment` | `'start' \| 'center' \| 'end'` | `'center'` | Aligns the visible range to the selection on initial render. |
| `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; weeks?: number; days?: number}` | `{months: 1}` | Visible time range. Use `{ months: n }` for month view, `{ weeks: n }` for week view, or `{ days: n }` for day view. |
| `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` | Formatted heading for the visible range. Supports `offset` (for multi-month layouts) and `format` (month/year/day options). |
| `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. |
### Year Picker Parts
Year picker subcomponents inherit formatting props from React Aria [`useCalendarHeading`](https://react-aria.adobe.com/useCalendar#usecalendarheading) and [`useCalendarYearPicker`](https://react-aria.adobe.com/useCalendar#usecalendaryearpicker).
| Component | Prop | Type | Default | Description |
| ----------------------------------- | -------------- | ---------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `Calendar.YearPickerTriggerHeading` | `format` | `DateFormatterOptions` | - | Customize month/year label (e.g. `{month: 'short'}`). |
| `Calendar.YearPickerTriggerHeading` | `offset` | `{months?: number}` | - | Shift the heading relative to the focused date (multi-month layouts). |
| `Calendar.YearPickerGrid` | `format` | `DateFormatterOptions` | `{year: 'numeric'}` | Customize year cell labels (era, calendar system, etc.). |
| `Calendar.YearPickerGrid` | `visibleYears` | `number` | min–max span or `20` | Number of years shown in the sliding window. Defaults to the full range between `minValue` and `maxValue` when both are set. |
### 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
## Related Components
## 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
# DateField
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/date-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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
## Usage
```tsx
import { DateField } from '@heroui/react';
```
```tsx
"use client";
import {DateField, Label} from "@heroui/react";
export function Basic() {
return (
Date
{(segment) => }
);
}
```
## Anatomy
```tsx
import {DateField, Label, Description, FieldError} from '@heroui/react';
export default () => (
{(segment) => }
)
```
> **DateField** combines label, date input, description, and error into a single accessible component.
## Examples
### With Icons
Add prefix or suffix icons to enhance the date field.
```tsx
"use client";
import {Calendar} from "@gravity-ui/icons";
import {DateField, Label} from "@heroui/react";
export function WithPrefixIcon() {
return (
Date
{(segment) => }
);
}
```
```tsx
"use client";
import {Calendar} from "@gravity-ui/icons";
import {DateField, Label} from "@heroui/react";
export function WithSuffixIcon() {
return (
Date
{(segment) => }
);
}
```
```tsx
"use client";
import {Calendar, ChevronDown} from "@gravity-ui/icons";
import {DateField, Description, Label} from "@heroui/react";
export function WithPrefixAndSuffix() {
return (
Date
{(segment) => }
Enter a date
);
}
```
### 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 "@heroui/react";
export function Variants() {
return (
Primary variant
{(segment) => }
Secondary variant
{(segment) => }
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` on DateField.Group to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {Calendar} from "@gravity-ui/icons";
import {DateField, Description, Label, Surface} from "@heroui/react";
export function OnSurface() {
return (
Date
{(segment) => }
Enter a date
Appointment date
{(segment) => }
Enter a date for your appointment
);
}
```
### With Description
```tsx
"use client";
import {DateField, Description, Label} from "@heroui/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 "@heroui/react";
export function Required() {
return (
Date
{(segment) => }
Start date
{(segment) => }
Required field
);
}
```
### Disabled State
```tsx
"use client";
import {DateField, Description, Label} from "@heroui/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
);
}
```
### Full Width
```tsx
"use client";
import {Calendar, ChevronDown} from "@gravity-ui/icons";
import {DateField, Label} from "@heroui/react";
export function FullWidth() {
return (
Date
{(segment) => }
Date
{(segment) => }
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
"use client";
import {DateField, FieldError, Label} from "@heroui/react";
export function Invalid() {
return (
Date
{(segment) => }
Please enter a valid date
Date
{(segment) => }
Date must be in the future
);
}
```
### Granularity
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {CircleQuestion} from "@gravity-ui/icons";
import {DateField, Label, ListBox, Select, Tooltip} from "@heroui/react";
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 "@heroui/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
);
}
```
### Form Example
Complete form example with validation and submission handling.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar} from "@gravity-ui/icons";
import {Button, DateField, Description, FieldError, Form, Label} from "@heroui/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 todayDate = today(getLocalTimeZone());
const isInvalid = value !== null && value.compare(todayDate) < 0;
const handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("Date submitted:", {date: value});
setValue(null);
setIsSubmitting(false);
}, 1500);
};
return (
);
}
```
### 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 "@heroui/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
)}
);
}
```
### Render Function
```tsx
"use client";
import {DateField, Label} from "@heroui/react";
export function RenderFunction() {
return (
}
>
}>Date
}>
}>
{(segment) => }
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {DateField, Label} from "@heroui/react";
export function CustomStyles() {
return (
Due date
{(segment) => }
);
}
```
### Global CSS
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;
}
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
* `.date-field` – Root container with minimal styling (`flex flex-col gap-1`)
> **Note:** Child components ([Label](/docs/react/components/label), [Description](/docs/react/components/description), [FieldError](/docs/react/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
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 `@heroui/react`
* **DateField.Group** - Date input group component (documented below)
* **DateField.Input** - Input component with segmented editing from `@heroui/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 `@heroui/react`
* **FieldError** - Validation error message from `@heroui/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 '@heroui/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.
### Render Props
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
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
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
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
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
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
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
## Related Components
## 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
# DatePicker
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/date-picker
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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
## Usage
```tsx
import { DatePicker, DateField, Calendar, Label } from '@heroui/react';
```
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@heroui/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 '@heroui/react';
export default () => (
{(segment) => }
{(day) => {day} }
{(date) => }
)
```
## Examples
### Disabled
```tsx
"use client";
import {Calendar, DateField, DatePicker, Description, Label} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
return (
Date
{(segment) => }
This date picker is disabled.
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Controlled
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Calendar, DateField, DatePicker, Description, Label} from "@heroui/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 "@heroui/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 "@heroui/react";
import type {DateValue} from "@internationalized/date";
import {
Calendar,
DateField,
DatePicker,
Label,
ListBox,
Select,
Switch,
TimeField,
} from "@heroui/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
);
}
```
### Form Example
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {
Button,
Calendar,
DateField,
DatePicker,
Description,
FieldError,
Form,
Label,
} from "@heroui/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.SyntheticEvent) => {
event.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
setTimeout(() => {
setValue(null);
setIsSubmitting(false);
}, 1200);
};
return (
);
}
```
### 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 "@heroui/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}) => }
);
}
```
### Render Function
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@heroui/react";
export function RenderFunction() {
return (
}
>
}>Date
}
>
}>
{(segment) => (
}
segment={segment}
/>
)}
}
>
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 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 "@heroui/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)
## Customization
### Tailwind CSS
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@heroui/react";
export function CustomStyles() {
return (
Event date
{(segment) => }
{(day) => (
{day}
)}
{(date) => (
)}
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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
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
## Related Components
## 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
# DateRangePicker
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/date-range-picker
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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
## Usage
```tsx
import { DateField, DateRangePicker, Label, RangeCalendar } from '@heroui/react';
```
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@heroui/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 '@heroui/react';
export default () => (
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
)
```
## Examples
### Disabled
```tsx
"use client";
import {DateField, DateRangePicker, Description, Label, RangeCalendar} from "@heroui/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}) => }
);
}
```
### Controlled
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, DateField, DateRangePicker, Description, Label, RangeCalendar} from "@heroui/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 "@heroui/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 "@heroui/react";
import type {DateValue} from "@internationalized/date";
import {
DateField,
DateRangePicker,
Label,
ListBox,
RangeCalendar,
Select,
Separator,
Switch,
TimeField,
useLocale,
} from "@heroui/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
);
}
```
### Form Example
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {
Button,
DateField,
DateRangePicker,
Description,
FieldError,
Form,
Label,
RangeCalendar,
} from "@heroui/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.SyntheticEvent) => {
event.preventDefault();
if (!value || isInvalid) return;
setIsSubmitting(true);
setTimeout(() => {
setValue(null);
setIsSubmitting(false);
}, 1200);
};
return (
);
}
```
### 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 "@heroui/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}) => }
);
}
```
### Render Function
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@heroui/react";
export function RenderFunction() {
return (
}
startName="startDate"
>
Trip dates
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 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 "@heroui/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)
## Customization
### Tailwind CSS
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@heroui/react";
export function CustomStyles() {
return (
Stay dates
{(segment) => }
{(segment) => }
{(day) => (
{day}
)}
{(date) => }
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### 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
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
## Related Components
## 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
# RangeCalendar
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/range-calendar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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
## Usage
```tsx
import { RangeCalendar } from '@heroui/react';
```
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
export function Basic() {
return (
{(day) => {day} }
{(date) => }
);
}
```
## Anatomy
```tsx
import {RangeCalendar} from '@heroui/react';
export default () => (
{(day) => {day} }
{(date) => }
)
```
## Examples
### Disabled
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
export function Disabled() {
return (
{(day) => {day} }
{(date) => }
Range calendar is disabled
);
}
```
### Year Picker
`RangeCalendar.YearPickerTrigger`, `RangeCalendar.YearPickerGrid`, and their body/cell subcomponents provide an integrated year navigation pattern.
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
export function YearPicker() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### Default Value
```tsx
"use client";
import {RangeCalendar} from "@heroui/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 "@heroui/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 "@heroui/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 "@heroui/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
);
}
```
### Anchor-Based Unavailable Dates
When selecting a range, `isDateUnavailable` receives a second argument, `anchorDate`, set to the first selected date. Use it to limit which end dates are valid (for example, within 7 days of the start).
```tsx
"use client";
import type {CalendarDate, DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function AnchorUnavailableDates() {
const now = today(getLocalTimeZone());
const isDateUnavailable = (date: DateValue, anchorDate: CalendarDate | null) => {
return anchorDate != null && Math.abs(date.compare(anchorDate)) > 7;
};
return (
{(day) => {day} }
{(date) => }
After selecting a start date, only dates within 7 days are available
);
}
```
### Weeks in Month
Set `weeksInMonth` to a fixed value (for example, `6`) to keep the grid height stable when navigating between months.
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
export function WeeksInMonth() {
return (
{(day) => {day} }
{(date) => }
Always shows 6 weeks per month to avoid layout shift when navigating
);
}
```
### Week View
Set `visibleDuration={{ weeks: n }}` to show one or more weeks at a time. Navigation advances by the visible week range. Use `pageBehavior="single"` to move one week at a time when showing multiple weeks.
```tsx
"use client";
import {Label, ListBox, RangeCalendar, Select} from "@heroui/react";
import {useState} from "react";
const weekOptions = [
{id: "1", name: "1 week"},
{id: "2", name: "2 weeks"},
{id: "3", name: "3 weeks"},
{id: "4", name: "4 weeks"},
{id: "5", name: "5 weeks"},
{id: "6", name: "6 weeks"},
{id: "8", name: "8 weeks"},
] as const;
export function WeekView() {
const [weeks, setWeeks] = useState(1);
return (
value && setWeeks(Number(value))}
>
Visible weeks
{weekOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### Day View
Set `visibleDuration={{ days: n }}` to show a rolling window of consecutive days. Navigation advances by the visible day range. Use `pageBehavior="single"` to move one day at a time when showing multiple days.
```tsx
"use client";
import {Label, ListBox, RangeCalendar, Select} from "@heroui/react";
import {useState} from "react";
const dayOptions = [
{id: "1", name: "1 day"},
{id: "5", name: "5 days"},
{id: "7", name: "7 days"},
{id: "8", name: "8 days"},
{id: "10", name: "10 days"},
{id: "14", name: "14 days"},
{id: "21", name: "21 days"},
] as const;
export function DayView() {
const [days, setDays] = useState(5);
return (
value && setDays(Number(value))}
>
Visible days
{dayOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### 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 "@heroui/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
);
}
```
### Read Only
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/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 "@heroui/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 "@heroui/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 "@heroui/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)) && (
)}
>
)}
)}
);
}
```
### Real-World Example
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, RangeCalendar} from "@heroui/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}
);
}
```
### Multiple Months
Render multiple grids with `visibleDuration` and `offset` for booking and planning experiences.
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
export function MultipleMonths() {
return (
{(day) => {day} }
{(date) => }
{(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 "@heroui/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.
## Customization
### Tailwind CSS
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
import {parseDate} from "@internationalized/date";
const cellClassName = [
"rounded-md",
"**:data-[slot=range-calendar-cell-button]:rounded-md",
"data-[outside-month=true]:text-muted data-[outside-month=true]:opacity-50",
"data-[hovered=true]:not-data-[selected=true]:**:data-[slot=range-calendar-cell-button]:bg-default",
"data-[today=true]:**:data-[slot=range-calendar-cell-button]:bg-success-soft",
"data-[today=true]:**:data-[slot=range-calendar-cell-button]:text-success-soft-foreground",
"data-[today=true]:data-[hovered=true]:not-data-[selected=true]:**:data-[slot=range-calendar-cell-button]:bg-success-soft-hover",
"data-[selected=true]:rounded-none data-[selected=true]:bg-success-soft",
"data-[outside-month=true]:data-[selected=true]:bg-default/20",
"data-[selection-start=true]:rounded-tl-md data-[selection-start=true]:rounded-bl-md",
"data-[selection-end=true]:rounded-tr-md data-[selection-end=true]:rounded-br-md",
"data-[selection-start=true]:**:data-[slot=range-calendar-cell-button]:bg-success",
"data-[selection-start=true]:**:data-[slot=range-calendar-cell-button]:text-success-foreground",
"data-[selection-start=true]:data-[pressed=true]:**:data-[slot=range-calendar-cell-button]:bg-success-hover",
"data-[selection-end=true]:**:data-[slot=range-calendar-cell-button]:bg-success",
"data-[selection-end=true]:**:data-[slot=range-calendar-cell-button]:text-success-foreground",
"data-[selection-end=true]:data-[pressed=true]:**:data-[slot=range-calendar-cell-button]:bg-success-hover",
].join(" ");
export function CustomStyles() {
return (
{(day) => (
{day}
)}
{(date) => }
);
}
```
### Global CSS
```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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### 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
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. |
| `weeksInMonth` | `number` | - | The number of weeks in a month. This overrides the default set by the locale. |
| `isDateUnavailable` | `(date: DateValue, anchorDate: CalendarDate \| null) => boolean` | - | Marks dates as unavailable. When `anchorDate` is set, it is the first date the user selected in the current range gesture. |
| `firstDayOfWeek` | `'sun' \| 'mon' \| 'tue' \| 'wed' \| 'thu' \| 'fri' \| 'sat'` | - | Overrides the locale default for the first day of the week. |
| `pageBehavior` | `'visible' \| 'single'` | `'visible'` | Whether paging advances by the visible duration or one unit at a time. |
| `selectionAlignment` | `'start' \| 'center' \| 'end'` | `'center'` | Aligns the visible range to the selection on initial render. |
| `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; weeks?: number; days?: number}` | `{months: 1}` | Visible time range. Use `{ months: n }` for month view, `{ weeks: n }` for week view, or `{ days: n }` for day view. |
| `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` | Formatted heading for the visible range. Supports `offset` (for multi-month layouts) and `format` (month/year/day options). |
| `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. |
### Year Picker Parts
Year picker subcomponents inherit formatting props from React Aria [`useCalendarHeading`](https://react-aria.adobe.com/useCalendar#usecalendarheading) and [`useCalendarYearPicker`](https://react-aria.adobe.com/useCalendar#usecalendaryearpicker).
| Component | Prop | Type | Default | Description |
| ---------------------------------------- | -------------- | ---------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `RangeCalendar.YearPickerTriggerHeading` | `format` | `DateFormatterOptions` | - | Customize month/year label (e.g. `{month: 'short'}`). |
| `RangeCalendar.YearPickerTriggerHeading` | `offset` | `{months?: number}` | - | Shift the heading relative to the focused date (multi-month layouts). |
| `RangeCalendar.YearPickerGrid` | `format` | `DateFormatterOptions` | `{year: 'numeric'}` | Customize year cell labels (era, calendar system, etc.). |
| `RangeCalendar.YearPickerGrid` | `visibleYears` | `number` | min–max span or `20` | Number of years shown in the sliding window. Defaults to the full range between `minValue` and `maxValue` when both are set. |
### 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
## Related Components
## 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
# TimeField
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/time-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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
## Usage
```tsx
import { TimeField } from '@heroui/react';
```
```tsx
"use client";
import {Label, TimeField} from "@heroui/react";
export function Basic() {
return (
Time
{(segment) => }
);
}
```
## Anatomy
```tsx
import {TimeField, Label, Description, FieldError} from '@heroui/react';
export default () => (
{(segment) => }
)
```
> **TimeField** combines label, time input, description, and error into a single accessible component.
## Examples
### With Icons
Add prefix or suffix icons to enhance the time field.
```tsx
"use client";
import {Clock} from "@gravity-ui/icons";
import {Label, TimeField} from "@heroui/react";
export function WithPrefixIcon() {
return (
Time
{(segment) => }
);
}
```
```tsx
"use client";
import {Clock} from "@gravity-ui/icons";
import {Label, TimeField} from "@heroui/react";
export function WithSuffixIcon() {
return (
Time
{(segment) => }
);
}
```
```tsx
"use client";
import {ChevronDown, Clock} from "@gravity-ui/icons";
import {Description, Label, TimeField} from "@heroui/react";
export function WithPrefixAndSuffix() {
return (
Time
{(segment) => }
Enter a time
);
}
```
### On Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` on TimeField.Group to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {Clock} from "@gravity-ui/icons";
import {Description, Label, Surface, TimeField} from "@heroui/react";
export function OnSurface() {
return (
Time
{(segment) => }
Enter a time
Appointment time
{(segment) => }
Enter a time for your appointment
);
}
```
### With Description
```tsx
"use client";
import {Description, Label, TimeField} from "@heroui/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 "@heroui/react";
export function Required() {
return (
Time
{(segment) => }
Appointment time
{(segment) => }
Required field
);
}
```
### Disabled State
```tsx
"use client";
import {Description, Label, TimeField} from "@heroui/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
);
}
```
### Full Width
```tsx
"use client";
import {ChevronDown, Clock} from "@gravity-ui/icons";
import {Label, TimeField} from "@heroui/react";
export function FullWidth() {
return (
Time
{(segment) => }
Time
{(segment) => }
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
"use client";
import {FieldError, Label, TimeField} from "@heroui/react";
export function Invalid() {
return (
Time
{(segment) => }
Please enter a valid time
Time
{(segment) => }
Time must be within business hours
);
}
```
### Controlled
Control the value to synchronize with other components or state management.
```tsx
"use client";
import type {TimeValue} from "@heroui/react";
import {Button, Description, Label, TimeField} from "@heroui/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
);
}
```
### Form Example
Complete form example with validation and submission handling.
```tsx
"use client";
import type {Time} from "@internationalized/date";
import {Clock} from "@gravity-ui/icons";
import {Button, Description, FieldError, Form, Label, TimeField} from "@heroui/react";
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.SyntheticEvent) => {
e.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("Time submitted:", {time: value});
setValue(null);
setIsSubmitting(false);
}, 1500);
};
return (
);
}
```
### 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 "@heroui/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
)}
);
}
```
### Render Function
```tsx
"use client";
import {Label, TimeField} from "@heroui/react";
export function RenderFunction() {
return (
}
>
Time
{(segment) => }
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {Description, Label, TimeField} from "@heroui/react";
export function CustomStyles() {
return (
Reminder time
Daily check-in notification.
{(segment) => }
);
}
```
### Global CSS
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;
}
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
* `.time-field` – Root container with minimal styling (`flex flex-col gap-1`)
> **Note:** Child components ([Label](/docs/react/components/label), [Description](/docs/react/components/description), [FieldError](/docs/react/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
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 `@heroui/react`
* **TimeField.Group** - Time input group component (documented below)
* **TimeField.Input** - Input component with segmented editing from `@heroui/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 `@heroui/react`
* **FieldError** - Validation error message from `@heroui/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 '@heroui/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.
### Render Props
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
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
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
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
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
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
## Related Components
## Related Components
* **Label**: Accessible label for form controls
* **FieldError**: Inline validation messages for form fields
* **Description**: Helper text for form fields
# Alert
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/alert
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(feedback)/alert.mdx
> Display important messages and notifications to users with status indicators
## Usage
```tsx
import { Alert } from '@heroui/react';
```
```tsx
import {Alert, Button, CloseButton, Spinner} from "@heroui/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
```tsx
import { Alert } from '@heroui/react';
export default () => (
)
```
## Customization
### Tailwind CSS
```tsx
import {Alert, Button, CloseButton} from "@heroui/react";
export function CustomStyles() {
return (
Payment method expires soon
Your Visa ending in 4242 expires on March 28. Update billing to avoid interrupting your
Pro subscription.
Update billing
Update billing
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/alert.css)):
#### Base Classes \[!toc]
* `.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 \[!toc]
* `.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
| 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
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ----------------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator icon (defaults to status icon) |
### Alert.Content
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ----------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Content (typically Title and Description) |
### Alert.Title
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert title text |
### Alert.Description
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | The alert description text |
## Related Components
## Related Components
* **CloseButton**: Button for dismissing overlays
* **Button**: Allows a user to perform an action
* **Spinner**: Loading indicator
# Meter
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/meter
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(feedback)/meter.mdx
> A meter represents a quantity within a known range, or a fractional value.
## Usage
```tsx
import { Meter, Label } from '@heroui/react';
```
```tsx
import {Label, Meter} from "@heroui/react";
export function Basic() {
return (
Storage
);
}
```
## Anatomy
```tsx
import { Meter, Label } from '@heroui/react';
export default () => (
Storage
);
```
## Examples
### Sizes
```tsx
import {Label, Meter} from "@heroui/react";
export function Sizes() {
return (
Small
Medium
Large
);
}
```
### Colors
```tsx
import {Label, Meter} from "@heroui/react";
export function Colors() {
return (
Default
Accent
Success
Warning
Danger
);
}
```
### Without Label
When no visible label is needed, use `aria-label` for accessibility.
```tsx
import {Meter} from "@heroui/react";
export function WithoutLabel() {
return (
);
}
```
### Custom Value Scale
Use `minValue`, `maxValue`, and `formatOptions` to customize the value range and display format.
```tsx
import {Label, Meter} from "@heroui/react";
export function CustomValue() {
return (
Revenue
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Label, Meter} from "@heroui/react";
export function CustomStyles() {
return (
Storage used
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/meter.css)):
#### Base & Element Classes \[!toc]
* `.meter` - Base container (grid layout)
* `.meter__output` - Value text display
* `.meter__track` - Track background
* `.meter__fill` - Filled portion of the track
#### Size Classes \[!toc]
* `.meter--sm` - Small size variant (thinner track)
* `.meter--md` - Medium size variant (default)
* `.meter--lg` - Large size variant (thicker track)
#### Color Classes \[!toc]
* `.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
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 |
### Render Props
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 |
## Related Components
# ProgressBar
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/progress-bar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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.
## Usage
```tsx
import { ProgressBar, Label } from '@heroui/react';
```
```tsx
import {Label, ProgressBar} from "@heroui/react";
export function Basic() {
return (
Loading
);
}
```
## Anatomy
```tsx
import { ProgressBar, Label } from '@heroui/react';
export default () => (
Loading
);
```
## Examples
### Sizes
```tsx
import {Label, ProgressBar} from "@heroui/react";
export function Sizes() {
return (
);
}
```
### Colors
```tsx
import {Label, ProgressBar} from "@heroui/react";
export function Colors() {
return (
Default
Accent
Success
Warning
Danger
);
}
```
### Without Label
When no visible label is needed, use `aria-label` for accessibility.
```tsx
import {ProgressBar} from "@heroui/react";
export function WithoutLabel() {
return (
);
}
```
### Indeterminate
Use `isIndeterminate` when progress cannot be determined.
```tsx
import {Label, ProgressBar} from "@heroui/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 "@heroui/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}
))}
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Label, ProgressBar} from "@heroui/react";
export function CustomStyles() {
return (
Uploading resume.pdf
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/progress-bar.css)):
#### Base & Element Classes \[!toc]
* `.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 \[!toc]
* `.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 \[!toc]
* `.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
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 |
### Render Props
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 |
## Related Components
# ProgressCircle
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/progress-circle
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(feedback)/progress-circle.mdx
> A circular progress indicator that shows determinate or indeterminate progress.
## Usage
```tsx
import { ProgressCircle } from '@heroui/react';
```
```tsx
import {ProgressCircle} from "@heroui/react";
export function Basic() {
return (
);
}
```
## Anatomy
```tsx
import { ProgressCircle } from '@heroui/react';
export default () => (
);
```
## Examples
### Sizes
```tsx
import {ProgressCircle} from "@heroui/react";
export function Sizes() {
return (
);
}
```
### Colors
```tsx
import {ProgressCircle} from "@heroui/react";
export function Colors() {
return (
);
}
```
### Indeterminate
Use `isIndeterminate` when progress cannot be determined.
```tsx
import {ProgressCircle} from "@heroui/react";
export function Indeterminate() {
return (
);
}
```
### With Label
```tsx
import {Label, ProgressCircle} from "@heroui/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 "@heroui/react";
export function CustomSvg() {
return (
);
}
```
## Customization
### Tailwind CSS
```tsx
import {ProgressCircle} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/progress-circle.css)):
#### Base & Element Classes \[!toc]
* `.progress-circle` - Base container
* `.progress-circle__track` - SVG element
* `.progress-circle__track-circle` - Background circle
* `.progress-circle__fill-circle` - Progress arc
#### Size Classes \[!toc]
* `.progress-circle--sm` - Small size variant
* `.progress-circle--md` - Medium size variant (default)
* `.progress-circle--lg` - Large size variant
#### Color Classes \[!toc]
* `.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
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 |
### Render Props
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 |
## Related Components
# Skeleton
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/skeleton
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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.
## Usage
```tsx
import { Skeleton } from '@heroui/react';
```
```tsx
import {Skeleton} from "@heroui/react";
export function Basic() {
return (
);
}
```
## Examples
### Text Content
```tsx
import {Skeleton} from "@heroui/react";
export function TextContent() {
return (
);
}
```
### User Profile
```tsx
import {Skeleton} from "@heroui/react";
export function UserProfile() {
return (
);
}
```
### List Items
```tsx
import {Skeleton} from "@heroui/react";
export function List() {
return (
{Array.from({length: 3}).map((_, index) => (
))}
);
}
```
### Grid
```tsx
import {Skeleton} from "@heroui/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 "@heroui/react";
export function SingleShimmer() {
return (
);
}
```
### Animation Types
```tsx
import {Skeleton} from "@heroui/react";
export function AnimationTypes() {
return (
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Skeleton} from "@heroui/react";
const bone = "animate-shine rounded-lg bg-neutral-200/90 dark:bg-neutral-800/90";
export function CustomStyles() {
return (
);
}
```
### Global CSS
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 */
}
}
```
### 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.
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/skeleton.css)):
#### Base Class \[!toc]
`.skeleton` - Base skeleton styles with background and rounded corners
#### Animation Variant Classes \[!toc]
* `.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 \[!toc]
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 \[!toc]
The pulse animation uses Tailwind's built-in `animate-pulse` utility:
```css
.skeleton--pulse {
@apply animate-pulse;
}
```
#### No Animation \[!toc]
For static skeletons without any animation:
```css
.skeleton--none {
/* No animation styles applied */
}
```
## API Reference
### Skeleton
| 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 |
## Related Components
## Related Components
* **Card**: Content container with header, body, and footer
* **Avatar**: Display user profile images
# Spinner
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/spinner
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(feedback)/spinner.mdx
> A loading indicator component to show pending states
## Usage
```tsx
import { Spinner } from '@heroui/react';
```
```tsx
import {Spinner} from "@heroui/react";
export function SpinnerBasic() {
return (
);
}
```
## Examples
### Colors
```tsx
import {Spinner} from "@heroui/react";
export function SpinnerColors() {
return (
Current
Accent
Success
Warning
Danger
);
}
```
### Sizes
```tsx
import {Spinner} from "@heroui/react";
export function SpinnerSizes() {
return (
Small
Medium
Large
Extra Large
);
}
```
### Speed
Apply an animation utility to change how fast a single spinner rotates. Pair it with
`motion-reduce:animate-none` so it stays still for users who prefer reduced motion.
```tsx
import {Spinner} from "@heroui/react";
export function SpinnerSpeed() {
return (
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Spinner} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### Global CSS
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);
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/spinner.css)):
#### Base & Size Classes \[!toc]
* `.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 \[!toc]
* `.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
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | ---------- | ---------------------------- |
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size of the spinner |
| `color` | `"current" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | Color variant of the spinner |
| `className` | `string` | - | Additional CSS classes |
## Related Components
# CheckboxGroup
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/checkbox-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/checkbox-group.mdx
> A checkbox group component for managing multiple checkbox selections
## Usage
```tsx
import { CheckboxGroup, Checkbox, Label, Description } from '@heroui/react';
```
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/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
```tsx
import {CheckboxGroup, Checkbox, Label, Description, FieldError} from '@heroui/react';
export default () => (
{/* Optional */}
Label {/* plain text — the clickable label */}
{/* Optional per-checkbox help text */}
{/* Optional */}
);
```
## Examples
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Checkbox, CheckboxGroup, Description, Label, Surface} from "@heroui/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
);
}
```
### Disabled
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/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
);
}
```
### Indeterminate
```tsx
"use client";
import {Checkbox, CheckboxGroup} from "@heroui/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 "@heroui/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 "@heroui/react";
export function Validation() {
return (
);
}
```
### Features and Add-ons Example
```tsx
import {Bell, Comment, Envelope} from "@gravity-ui/icons";
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
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}
))}
);
}
```
### With Custom Indicator
```tsx
"use client";
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/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
);
}
```
### Render Function
```tsx
"use client";
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
export function RenderFunction() {
return (
}>
Select your interests
Choose all that apply
Coding
Love building software
Design
Enjoy creating beautiful interfaces
Writing
Passionate about content creation
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
const controlClassName = "bg-success-soft before:bg-success";
const indicatorClassName =
"**:data-[slot=checkbox-default-indicator--checkmark]:text-success-foreground";
const channels = [
{label: "Email", value: "email"},
{label: "SMS", value: "sms"},
{label: "Push", value: "push"},
] as const;
export function CustomStyles() {
return (
Notification channels
Choose how we should reach you for account updates.
{channels.map(({label, value}) => (
{label}
))}
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/checkbox-group.css)):
#### Base Classes \[!toc]
* `.checkbox-group` - Base checkbox group container
## API Reference
### CheckboxGroup
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. |
### Render Props
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 |
## Related Components
## Related Components
* **Checkbox**: Binary choice input control
* **Label**: Accessible label for form controls
* **Fieldset**: Group related form controls with legends
# Checkbox
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/checkbox
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/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.
## Usage
```tsx
import { Checkbox } from '@heroui/react';
```
```tsx
import {Checkbox} from "@heroui/react";
export function Basic() {
return (
Accept terms and conditions
);
}
```
## Anatomy
```tsx
import { Checkbox, Description, FieldError } from '@heroui/react';
export default () => (
Label {/* plain text — the clickable label + accessible name */}
{/* Optional — field-level help text */}
{/* Optional — validation message */}
);
```
## Examples
### 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} from "@heroui/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
);
}
```
### Full Rounded
```tsx
import {Checkbox, Label} from "@heroui/react";
export function FullRounded() {
return (
Rounded checkboxes
Small size
Default size
Large size
Extra large size
);
}
```
### Disabled
```tsx
import {Checkbox, Description} from "@heroui/react";
export function Disabled() {
return (
Premium Feature
This feature is coming soon
);
}
```
### External Label
```tsx
import {Checkbox, Label} from "@heroui/react";
export function ExternalLabel() {
return (
Send me marketing emails
);
}
```
### With Description
```tsx
import {Checkbox, Description} from "@heroui/react";
export function WithDescription() {
return (
Email notifications
Get notified when someone mentions you in a comment
);
}
```
### Default Selected
```tsx
import {Checkbox} from "@heroui/react";
export function DefaultSelected() {
return (
Enable email notifications
);
}
```
### Invalid
```tsx
import {Checkbox, FieldError} from "@heroui/react";
export function Invalid() {
return (
I agree to the terms
You must accept the terms to continue
);
}
```
### Controlled
```tsx
"use client";
import {Checkbox} from "@heroui/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} from "@heroui/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)
);
}
```
### Form Integration
```tsx
"use client";
import {Button, Checkbox} from "@heroui/react";
import React from "react";
export function Form() {
const handleSubmit = (e: React.SyntheticEvent) => {
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 (
);
}
```
### Render Props
```tsx
"use client";
import {Checkbox, Description} from "@heroui/react";
export function RenderProps() {
return (
{({isSelected}) => (
<>
{isSelected ? "Terms accepted" : "Accept terms"}
{isSelected ? "Thank you for accepting" : "Please read and accept the terms"}
>
)}
);
}
```
### Render Function
```tsx
"use client";
import {Checkbox} from "@heroui/react";
export function RenderFunction() {
return (
}>
Accept terms and conditions
);
}
```
### Custom Indicator
```tsx
"use client";
import {Checkbox} from "@heroui/react";
export function CustomIndicator() {
return (
{({isSelected}) =>
isSelected ? (
) : null
}
Heart
{({isSelected}) =>
isSelected ? (
) : null
}
Plus
{({isIndeterminate}) =>
isIndeterminate ? (
) : null
}
Indeterminate
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Checkbox} from "@heroui/react";
export function CustomStyles() {
return (
Custom styled checkbox
);
}
```
### Global CSS
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 items-center gap-3;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/checkbox.css)):
#### Base Classes \[!toc]
* `.checkbox` - Base checkbox container (the field)
* `.checkbox__content` - Clickable label wrapping the control and label text
* `.checkbox__control` - Checkbox control box
* `.checkbox__indicator` - Checkbox checkmark indicator
### 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"]` on `Checkbox.Control` (button)
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on the button (shows focus ring on control)
* **Disabled**: `[data-disabled="true"]` on the field (reduced opacity, including help text)
* **Pressed**: `:active` or `[data-pressed="true"]`
## API Reference
### Checkbox
Inherits from [React Aria CheckboxField](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 |
| `isRequired` | `boolean` | `false` | Whether the checkbox must be selected |
| `validate` | `(value: boolean) => ValidationError \| true \| null \| undefined` | - | Custom validation function |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA |
| `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: CheckboxFieldRenderProps) => React.ReactNode` | - | Checkbox content or field render prop |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Checkbox.Content
The clickable `` that wraps the control and label text. Put `Checkbox.Control` and the `Label` inside it; keep `Description`/`FieldError` as siblings of `Checkbox.Content`. For a checkbox with no label, omit the `Label` and pass an `aria-label` on `Checkbox`.
| Prop | Type | Default | Description |
| ----------- | --------------------------------------------------------------------------- | ------- | --------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: CheckboxButtonRenderProps) => React.ReactNode` | - | Button content (control + label), or a button render prop |
| `className` | `string \| (values: CheckboxButtonRenderProps) => string` | - | Classes applied to the clickable label |
### CheckboxFieldRenderProps
When using a render prop on the root `Checkbox`, these field-level values are provided:
| Prop | Type | Description |
| ----------------- | --------- | ------------------------------------------------- |
| `isSelected` | `boolean` | Whether the checkbox is currently checked |
| `isIndeterminate` | `boolean` | Whether the checkbox is in an indeterminate state |
| `isDisabled` | `boolean` | Whether the checkbox is disabled |
| `isReadOnly` | `boolean` | Whether the checkbox is read only |
| `isInvalid` | `boolean` | Whether the checkbox is invalid |
| `isRequired` | `boolean` | Whether the checkbox is required |
### CheckboxButtonRenderProps
`Checkbox.Control` and `Checkbox.Indicator` use button-level render props (`isHovered`, `isPressed`, `isFocusVisible`, etc.). Pass a function as `Checkbox.Control` children or to `Checkbox.Indicator` to access them.
## Related Components
## Related Components
* **Label**: Accessible label for form controls
* **CheckboxGroup**: Group of checkboxes with shared state
* **Description**: Helper text for form fields
# Description
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/description
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/description.mdx
> Provides supplementary text for form fields and other components
## Usage
```tsx
import { Description } from '@heroui/react';
```
```tsx
import {Description, Input, Label} from "@heroui/react";
export function Basic() {
return (
Email
We'll never share your email with anyone else.
);
}
```
## 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 '@heroui/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.
## Customization
### Tailwind CSS
```tsx
import {Description, Input, Label} from "@heroui/react";
export function CustomStyles() {
return (
Workspace URL
Lowercase letters and hyphens only. Used in app.heroui.com/acme
);
}
```
### Global CSS
To customize the Description component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.description {
@apply text-muted;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Description component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/description.css)):
#### Base Classes \[!toc]
* `.description` - Base description styles with `muted` text color
## API Reference
### Description
| 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
## Related Components
## 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
# ErrorMessage
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/error-message
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/error-message.mdx
> A low-level error message component for displaying errors
## Usage
```tsx
import { ErrorMessage } from '@heroui/react';
```
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Description, ErrorMessage, Label, Tag, TagGroup} from "@heroui/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>}
);
}
```
`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.
## Anatomy
```tsx
import { TagGroup, Tag, Label, Description, ErrorMessage } from '@heroui/react';
```
## 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/react/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/react/components/field-error) and the [Form guide](/docs/react/components/form) for examples and best practices.
## Customization
### Tailwind CSS
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Description, ErrorMessage, Label, Tag, TagGroup} from "@heroui/react";
import {useMemo, useState} from "react";
export function CustomStyles() {
const [selected, setSelected] = useState>(new Set());
const isInvalid = useMemo(() => Array.from(selected).length === 0, [selected]);
return (
Topics
API
Design
Docs
Pick at least one topic
{!!isInvalid && <>Choose at least one topic>}
);
}
```
### Global CSS
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-sm font-medium text-danger;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/error-message.css)):
#### Base Classes \[!toc]
* `.error-message` - Base error message styles with danger color and text truncation
#### Slot Classes \[!toc]
* `[slot="errorMessage"]` - ErrorMessage slot styles for React Aria integration
## API Reference
### ErrorMessage
| 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
## Related Components
## Related Components
* **TagGroup**: Focusable list of tags with selection and removal support
# FieldError
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/field-error
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/field-error.mdx
> Displays validation error messages for form fields
## Usage
```tsx
import { FieldError } from '@heroui/react';
```
```tsx
"use client";
import {FieldError, Input, Label, TextField} from "@heroui/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
);
}
```
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.
## 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}
))}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {FieldError, Input, Label, TextField} from "@heroui/react";
import {useState} from "react";
export function CustomStyles() {
const [value, setValue] = useState("jr");
const isInvalid = value.length > 0 && value.length < 3;
return (
Handle
setValue(e.target.value)}
/>
Handle must be at least 3 characters
);
}
```
### Global CSS
To customize the FieldError component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.field-error {
@apply font-medium text-danger;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The FieldError component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/field-error.css)):
#### Base Classes \[!toc]
* `.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
## API Reference
### FieldError
| 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
## Related Components
## 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
# Fieldset
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/fieldset
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/fieldset.mdx
> Group related form controls with legends, descriptions, and actions
## Usage
```tsx
import { Fieldset } from '@heroui/react';
```
```tsx
"use client";
import {FloppyDisk} from "@gravity-ui/icons";
import {
Button,
Description,
FieldError,
FieldGroup,
Fieldset,
Form,
Input,
Label,
TextArea,
TextField,
} from "@heroui/react";
export function Basic() {
const onSubmit = (e: React.SyntheticEvent) => {
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 (
);
}
```
## Anatomy
```tsx
import { Fieldset } from '@heroui/react';
export default () => (
{/* form fields go here */}
{/* action buttons go here */}
)
```
## Examples
### In Surface
When used inside a [Surface](/docs/react/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 {FloppyDisk} from "@gravity-ui/icons";
import {
Button,
Description,
FieldError,
Fieldset,
Form,
Input,
Label,
Surface,
TextArea,
TextField,
} from "@heroui/react";
import React from "react";
export function OnSurface() {
const onSubmit = (e: React.SyntheticEvent) => {
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
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {FloppyDisk} from "@gravity-ui/icons";
import {
Button,
Description,
FieldError,
FieldGroup,
Fieldset,
Form,
Input,
Label,
TextArea,
TextField,
} from "@heroui/react";
const shell =
"rounded-xl border border-border/70 bg-linear-to-b from-neutral-50/90 to-white p-4 ring-1 ring-black/5 dark:from-neutral-900/80 dark:to-neutral-900 dark:ring-white/10";
const field =
"rounded-xl border border-border/80 bg-surface shadow-sm ring-1 ring-black/5 transition-[box-shadow,border-color] focus-visible:ring-2 focus-visible:ring-neutral-400/25 dark:ring-white/10 dark:focus-visible:ring-neutral-500/30";
export function CustomStyles() {
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
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
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Fieldset compound component exposes these CSS selectors:
#### Base Classes \[!toc]
* `.fieldset` – Root container
* `.fieldset__legend` – Legend element
* `.fieldset__field-group` – Wrapper for grouped fields
* `.fieldset__actions` – Action bar below the fields
## API Reference
### Fieldset
| 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
| 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
| 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
| 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. |
## Related Showcases
## Related Components
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **Label**: Accessible label for form controls
* **CheckboxGroup**: Group of checkboxes with shared state
# Form
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/form
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/form.mdx
> Wrapper component for form validation and submission handling
## Usage
```tsx
import { Form } from '@heroui/react';
```
```tsx
"use client";
import {Check} from "@gravity-ui/icons";
import {Button, Description, FieldError, Form, Input, Label, TextField} from "@heroui/react";
export function Basic() {
const onSubmit = (e: React.SyntheticEvent) => {
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
```tsx
import {Form, Button} from '@heroui/react';
export default () => (
{/* Form fields go here */}
)
```
## Examples
### Render Function
```tsx
"use client";
import {Check} from "@gravity-ui/icons";
import {Button, Description, FieldError, Form, Input, Label, TextField} from "@heroui/react";
export function RenderFunction() {
const onSubmit = (e: React.SyntheticEvent) => {
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
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {Button, Form, Input, Label, TextField} from "@heroui/react";
export function CustomStyles() {
return (
e.preventDefault()}
>
Work email
Continue
);
}
```
### Global CSS
To customize form layout and spacing, use the `className` prop on `` or add a project-level class with the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
`Form` renders a native ` ` element and focuses on validation and submission. It does not include dedicated BEM classes in `@heroui/styles`—apply container styles from global CSS, and use field components for control-level customization.
```css
@layer components {
.form-layout {
@apply flex flex-col gap-4 rounded-xl border border-border bg-surface p-4 shadow-sm;
}
}
```
```tsx
{/* TextField, Input, Button, etc. */}
```
For grouped fields with shared layout, use [Fieldset](./fieldset) and target `.fieldset`, `.fieldset__legend`, and related classes. For individual controls, see **Global CSS** on [TextField](./text-field), [Input](./input), [Label](./label), and [FieldError](./field-error).
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology for components that ship styles in `@heroui/styles`.
`Form` renders a native `` element without dedicated BEM classes. Apply layout, spacing, and surface styles through `className`. Field appearance and validation states come from child components such as `TextField`, `Input`, `Label`, `Description`, and `FieldError`. For structured multi-field layouts, compose with [Fieldset](./fieldset).
## API Reference
### Form
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: SyntheticEvent) => 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: SyntheticEvent) => void` | - | Handler called when the form is reset. |
| `onSubmit` | `(event: SyntheticEvent) => 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
import type {SyntheticEvent} from "react";
function handleSubmit(e: SyntheticEvent) {
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 HeroUI'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.
### 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).
## 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"`
## Related Showcases
## Related Components
## 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
# InputGroup
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/input-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/input-group.mdx
> Group related input controls with prefix and suffix elements for enhanced form fields
## Usage
```tsx
import { InputGroup } from '@heroui/react';
```
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function Default() {
return (
Email address
);
}
```
## Anatomy
```tsx
import {InputGroup, TextField, Label} from '@heroui/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/react/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.
## Examples
### 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 {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, Surface, TextField} from "@heroui/react";
export function OnSurface() {
return (
Email address
We'll never share this with anyone else
);
}
```
### Loading State
Show a loading spinner in the suffix to indicate processing.
```tsx
"use client";
import {InputGroup, Spinner, TextField} from "@heroui/react";
export function WithLoadingSuffix() {
return (
);
}
```
### Required Field
InputGroup respects the required state from its parent TextField.
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, TextField} from "@heroui/react";
export function Required() {
return (
Email address
Set a price
$
USD
What customers would pay
);
}
```
### Disabled State
InputGroup respects the disabled state from its parent TextField.
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function Disabled() {
return (
Email address
Set a price
$
USD
);
}
```
### Full Width
```tsx
import {Envelope, Eye} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function FullWidth() {
return (
Email address
Password
);
}
```
### Text Prefix
Use text as a prefix, such as currency symbols or protocol prefixes.
```tsx
"use client";
import {InputGroup, Label, TextField} from "@heroui/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 "@heroui/react";
export function WithTextSuffix() {
return (
Website
.com
);
}
```
### Icon Prefix and Text Suffix
Combine an icon prefix with a text suffix.
```tsx
"use client";
import {Globe} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
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 {Copy} from "@gravity-ui/icons";
import {Button, InputGroup, Label, TextField} from "@heroui/react";
export function WithCopySuffix() {
return (
Website
);
}
```
### Icon Prefix and Copy Button
Combine an icon prefix with an interactive button suffix.
```tsx
"use client";
import {Copy, Globe} from "@gravity-ui/icons";
import {Button, InputGroup, Label, TextField} from "@heroui/react";
export function WithIconPrefixAndCopySuffix() {
return (
Website
);
}
```
### Password Toggle
Use a button in the suffix to toggle password visibility.
```tsx
"use client";
import {Eye, EyeSlash} from "@gravity-ui/icons";
import {Button, InputGroup, Label, TextField} from "@heroui/react";
import {useState} from "react";
export function PasswordWithToggle() {
const [isVisible, setIsVisible] = useState(false);
return (
Password
setIsVisible(!isVisible)}
>
{isVisible ? : }
);
}
```
### Keyboard Shortcut
Display keyboard shortcuts using the [Kbd](/docs/react/components/kbd) component.
```tsx
"use client";
import {InputGroup, Kbd, TextField} from "@heroui/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 "@heroui/react";
export function WithBadgeSuffix() {
return (
Pro
);
}
```
### Validation
InputGroup automatically reflects invalid state from its parent TextField.
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {FieldError, InputGroup, Label, TextField} from "@heroui/react";
export function Invalid() {
return (
Email address
Please enter a valid email address
Set a price
$
USD
Price must be greater than 0
);
}
```
### With Prefix Icon
Add an icon before the input field.
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, TextField} from "@heroui/react";
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 {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, TextField} from "@heroui/react";
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 "@heroui/react";
export function WithPrefixAndSuffix() {
return (
Set a price
$
USD
What customers would pay
);
}
```
### 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 {ArrowUp, At, Microphone, PlugConnection, Plus} from "@gravity-ui/icons";
import {Button, InputGroup, Kbd, Spinner, TextField, Tooltip} from "@heroui/react";
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
);
}
```
### Usage Example
```tsx
import {InputGroup, TextField, Label, Button} from '@heroui/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 "@heroui/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
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function CustomStyles() {
return (
Work email
);
}
```
### Global CSS
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);
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
#### Base Classes \[!toc]
* `.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.
#### Variant Classes \[!toc]
* `.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
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. |
## Related Showcases
## Related Components
## 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
# InputOTP
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/input-otp
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/input-otp.mdx
> A one-time password input component for verification codes and secure authentication
## Usage
```tsx
import { InputOTP } from '@heroui/react';
```
```tsx
import {InputOTP, Label, Link} from "@heroui/react";
export function Basic() {
return (
Verify account
We've sent a code to a****@gmail.com
Didn't receive a code?
Resend
);
}
```
## Anatomy
```tsx
import { InputOTP } from '@heroui/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.
## Examples
### 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 "@heroui/react";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {InputOTP, Label, Link, Surface} from "@heroui/react";
export function OnSurface() {
return (
Verify account
We've sent a code to a****@gmail.com
Didn't receive a code?
Resend
);
}
```
### Disabled State
```tsx
import {Description, InputOTP, Label} from "@heroui/react";
export function Disabled() {
return (
Verify account
Code verification is currently disabled
);
}
```
### Four Digits
```tsx
import {InputOTP, Label} from "@heroui/react";
export function FourDigits() {
return (
Enter PIN
);
}
```
### Controlled
Control the value to synchronize with state, clear the input, or implement custom validation.
```tsx
"use client";
import {Description, InputOTP, Label} from "@heroui/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"
)}
);
}
```
### On Complete
Use the `onComplete` callback to trigger actions when all slots are filled.
```tsx
"use client";
import {Button, Form, InputOTP, Label, Spinner} from "@heroui/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.SyntheticEvent) => {
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 "@heroui/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.SyntheticEvent) => {
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
);
}
```
### With Pattern
Use the `pattern` prop to restrict input to specific characters. HeroUI exports common patterns like `REGEXP_ONLY_CHARS` and `REGEXP_ONLY_DIGITS`.
```tsx
import {Description, InputOTP, Label, REGEXP_ONLY_CHARS} from "@heroui/react";
export function WithPattern() {
return (
Enter code (letters only)
Only alphabetic characters are allowed
);
}
```
### With Validation
Use `isInvalid` together with validation messages to surface errors.
```tsx
"use client";
import {Button, Description, Form, InputOTP, Label} from "@heroui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState("");
const [isInvalid, setIsInvalid] = React.useState(false);
const onSubmit = (e: React.SyntheticEvent) => {
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
);
}
```
## Customization
### Tailwind CSS
```tsx
import {InputOTP, Label, Link} from "@heroui/react";
const slotClass =
"rounded-lg border-border/80 bg-default data-[active=true]:border-accent/40 data-[active=true]:bg-accent-soft";
export function CustomStyles() {
return (
Verify account
Resend code
);
}
```
### Global CSS
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-accent-500 ring-2 ring-accent-200;
}
.input-otp__separator {
@apply w-2 h-1 bg-border-strong rounded-full;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/input-otp.css)):
#### Base Classes \[!toc]
* `.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 \[!toc]
* `.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
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
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ------------------------------------- |
| `className` | `string` | - | Additional CSS classes for the group. |
| `children` | `React.ReactNode` | - | InputOTP.Slot components. |
### InputOTP.Slot
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ------------------------------------------- |
| `index` | `number` | - | **Required.** Zero-based index of the slot. |
| `className` | `string` | - | Additional CSS classes for the slot. |
### InputOTP.Separator
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ----------------------------------------- |
| `className` | `string` | - | Additional CSS classes for the separator. |
### Exported Patterns
HeroUI re-exports common regex patterns from input-otp for convenience:
```tsx
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from '@heroui/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)
## Related Components
## Related Components
* **Input**: Single-line text input built on React Aria
* **Form**: Form validation and submission handling
* **Surface**: Base container surface
# Input
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/input
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/input.mdx
> Primitive single-line text input component that accepts standard HTML attributes
## Usage
```tsx
import { Input } from '@heroui/react';
```
```tsx
import {Input} from "@heroui/react";
export function Basic() {
return ;
}
```
For validation, labels, and error messages, see **[TextField](/docs/react/components/text-field)**.
## Examples
### 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 "@heroui/react";
export function Variants() {
return (
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Input, Surface} from "@heroui/react";
export function OnSurface() {
return (
);
}
```
### Full Width
```tsx
import {Input} from "@heroui/react";
export function FullWidth() {
return (
);
}
```
### Input Types
```tsx
import {Input, Label} from "@heroui/react";
export function Types() {
return (
);
}
```
### Controlled
```tsx
"use client";
import {Input} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("heroui.com");
return (
setValue(event.target.value)}
/>
https://{value || "your-domain"}
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Input} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### Global CSS
The base class `.input` powers every instance. Override it once with `@layer components`.
```css
@layer components {
.input {
@apply rounded-lg border border-border bg-surface 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-accent ring-2 ring-accent/20;
}
&[data-invalid="true"] {
@apply border-danger bg-danger-soft text-danger;
}
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
#### Base Classes \[!toc]
* `.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
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/react/components/text-field)** with Input as a child component.
## Related Components
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **TextArea**: Multiline text input with focus management
* **Label**: Accessible label for form controls
# Label
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/label
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/label.mdx
> Renders an accessible label associated with form controls
## Usage
```tsx
import { Label } from '@heroui/react';
```
```tsx
import {Input, Label} from "@heroui/react";
export function Basic() {
return (
Name
);
}
```
## Examples
### With Required Indicator
```tsx
Email Address
```
### With Disabled State
```tsx
Username
```
### With Invalid State
```tsx
Password
```
## Customization
### Tailwind CSS
```tsx
import {Input, Label} from "@heroui/react";
export function CustomStyles() {
return (
Repository
);
}
```
### Global CSS
To customize the Label component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.label {
@apply font-semibold text-foreground;
}
.label--required::after {
@apply text-accent;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Label component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/label.css)):
#### Base Classes \[!toc]
* `.label` - Base label styles with text styling
#### State Modifier Classes \[!toc]
* `.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.
## API Reference
### Label
| 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
## 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
# NumberField
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/number-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/number-field.mdx
> Number input fields with increment/decrement buttons, validation, and internationalized formatting
## Usage
```tsx
import { NumberField } from '@heroui/react';
```
```tsx
import {Label, NumberField} from "@heroui/react";
export function Basic() {
return (
Width
);
}
```
## Anatomy
```tsx
import {NumberField, Label, Description, FieldError} from '@heroui/react';
export default () => (
)
```
> **NumberField** allows users to enter numeric values with optional increment/decrement buttons. It supports internationalized formatting, validation, and keyboard navigation.
## Examples
### 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 "@heroui/react";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Description, Label, NumberField, Surface} from "@heroui/react";
export function OnSurface() {
return (
Width
Enter the width in pixels
Percentage
Value must be between 0 and 100
);
}
```
### With Description
```tsx
import {Description, Label, NumberField} from "@heroui/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 "@heroui/react";
export function Required() {
return (
Quantity
Rating
Rate from 1 to 10
);
}
```
### Disabled State
```tsx
import {Description, Label, NumberField} from "@heroui/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 "@heroui/react";
export function FullWidth() {
return (
Width
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
import {FieldError, Label, NumberField} from "@heroui/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 "@heroui/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
);
}
```
### Step Values
Configure increment/decrement step values for precise control.
```tsx
import {Description, Label, NumberField} from "@heroui/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 "@heroui/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
);
}
```
### Form Example
Complete form integration with validation and submission handling.
```tsx
"use client";
import {Button, Description, FieldError, Form, Label, NumberField, Spinner} from "@heroui/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.SyntheticEvent) => {
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"
)}
);
}
```
### With Validation
Implement custom validation logic with controlled values.
```tsx
"use client";
import {Description, FieldError, Label, NumberField} from "@heroui/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
)}
);
}
```
### Custom Icons
Customize the increment and decrement button icons.
```tsx
import {Description, Label, NumberField} from "@heroui/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 "@heroui/react";
export function WithChevrons() {
return (
Number field with chevrons
);
}
```
### Render Function
```tsx
"use client";
import {Label, NumberField} from "@heroui/react";
export function RenderFunction() {
return (
}
>
Width
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Label, NumberField} from "@heroui/react";
export function CustomStyles() {
return (
Guests
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
#### Base Classes \[!toc]
* `.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
#### Variant Classes \[!toc]
* `.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/react/components/label), [Description](/docs/react/components/description), [FieldError](/docs/react/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
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. |
| `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.IncrementButton** - Button to increment the value
* **NumberField.DecrementButton** - Button to decrement the value
* **Label** - Field label component from `@heroui/react`
* **Description** - Helper text component from `@heroui/react`
* **FieldError** - Validation error message from `@heroui/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). |
### Render Props
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. |
## Related Showcases
## Related Components
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **FieldError**: Inline validation messages for form fields
# RadioGroup
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/radio-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/radio-group.mdx
> Radio group for selecting a single option from a list
## Usage
```tsx
import { RadioGroup, Radio } from '@heroui/react';
```
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/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
```tsx
import {RadioGroup, Radio, Label, Description, FieldError} from '@heroui/react';
export default () => (
{/* The clickable area: control + label */}
✓ {/* Custom indicator (optional) */}
Label {/* plain text — the clickable label */}
{/* Sibling — stays outside the button (announced via aria-describedby) */}
{/* Optional — per-radio validation message */}
{/* Optional — group-level validation */}
)
```
## Examples
### Horizontal Orientation
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
export function Horizontal() {
return (
Subscription plan
Starter
For side projects
Pro
Advanced reporting
Teams
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, Radio, RadioGroup} from "@heroui/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/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Description, Label, Radio, RadioGroup, Surface} from "@heroui/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
);
}
```
### Disabled
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/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
);
}
```
### Controlled
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@heroui/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 "@heroui/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 "@heroui/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}
}
);
}
```
### Delivery & Payment
### Custom Indicator
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@heroui/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
);
}
```
### Render Function
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
export function RenderFunction() {
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
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
const options = [
{description: "$12 billed every month", label: "Monthly", value: "monthly"},
{description: "$120 billed once a year", label: "Yearly", value: "yearly"},
] as const;
const contentClassName =
"group flex w-full items-start gap-3 rounded-xl border border-success/10 bg-success-soft/30 px-4 py-3 transition-colors data-[focus-visible=true]:ring-2 data-[focus-visible=true]:ring-success/15 data-[hovered=true]:bg-success-soft-hover data-[selected=true]:border-success/30 data-[selected=true]:bg-success-soft data-[selected=true]:data-[hovered=true]:bg-success-soft-hover";
const controlClassName =
"mt-0.5 size-5 shrink-0 rounded-full border border-border bg-default shadow-none group-data-[pressed=true]:scale-95 group-data-[selected=true]:border-transparent group-data-[selected=true]:bg-success group-data-[selected=true]:group-data-[pressed=true]:bg-success-hover";
const indicatorClassName =
"before:rounded-full before:bg-default group-data-[selected=true]:before:scale-50 group-data-[selected=true]:before:bg-success-foreground group-data-[selected=true]:group-data-[pressed=true]:before:scale-[0.57]";
export function CustomStyles() {
return (
Billing cycle
Choose how often you are charged.
{options.map(({description, label, value}) => (
{label}
{description}
))}
);
}
```
### Global CSS
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-accent;
}
.radio__indicator {
@apply bg-primary;
}
.radio__content {
@apply gap-1;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/radio-group.css)):
#### Base Classes \[!toc]
* `.radio-group` - Base radio group container
* `.radio` - Individual radio item
* `.radio__content` - Radio button (RAC label wrapper for control + label text)
* `.radio__control` - Radio control (circular button)
* `.radio__indicator` - Radio indicator (inner dot)
#### Modifier Classes \[!toc]
Disabled state is applied via `[data-disabled="true"]` / `[aria-disabled="true"]` (and `:disabled`) on `.radio` — there is no `.radio--disabled` modifier class.
### 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
| 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
| 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: RadioFieldRenderProps) => React.ReactNode` | - | Radio content or field render prop |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Radio.Control
Extends `React.HTMLAttributes`.
| Prop | Type | Default | Description |
| ---------- | ----------------- | ------- | ---------------------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | The content to render inside the control wrapper (typically Radio.Indicator) |
### Radio.Indicator
Extends `React.HTMLAttributes`.
| Prop | Type | Default | Description |
| ---------- | ------------------------------------------------------------------------ | ------- | ----------------------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: RadioButtonRenderProps) => React.ReactNode` | - | Optional content or render prop that receives the current radio button state. |
### Radio.Content
The clickable area of the radio (the `` wrapping the hidden input). Place `Radio.Control` and the `Label` inside it. `className` accepts a render function that receives `RadioButtonRenderProps`.
| Prop | Type | Default | Description |
| ---------- | ------------------------------------------------------------------------ | ------- | --------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: RadioButtonRenderProps) => React.ReactNode` | - | The clickable content (typically Radio.Control and Label) |
### RadioFieldRenderProps
When using a render prop on the root `Radio`, these field-level values are provided:
| Prop | Type | Description |
| ------------ | --------- | ---------------------------------------- |
| `isSelected` | `boolean` | Whether the radio is currently selected |
| `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 |
| `isRequired` | `boolean` | Whether the radio is required |
### RadioButtonRenderProps
`Radio.Control` and `Radio.Indicator` use button-level render props (`isHovered`, `isPressed`, `isFocusVisible`, etc.). Pass a function as `Radio.Control` children or to `Radio.Indicator` to access them.
## Related Showcases
## Related Components
## Related Components
* **Fieldset**: Group related form controls with legends
* **Surface**: Base container surface
* **Description**: Helper text for form fields
# SearchField
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/search-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/search-field.mdx
> Search input field with clear button and search icon
## Usage
```tsx
import { SearchField } from '@heroui/react';
```
```tsx
import {Label, SearchField} from "@heroui/react";
export function Basic() {
return (
Search
);
}
```
## Anatomy
```tsx
import {SearchField, Label, Description, FieldError} from '@heroui/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.
## Examples
### 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 "@heroui/react";
export function Variants() {
return (
Primary variant
Secondary variant
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Description, Label, SearchField, Surface} from "@heroui/react";
export function OnSurface() {
return (
Search
Enter keywords to search
Advanced search
Use filters to refine your search
);
}
```
### With Description
```tsx
import {Description, Label, SearchField} from "@heroui/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 "@heroui/react";
export function Required() {
return (
Search
Search query
Minimum 3 characters required
);
}
```
### Disabled State
```tsx
import {Description, Label, SearchField} from "@heroui/react";
export function Disabled() {
return (
Search
This search field is disabled
Search
This search field is disabled
);
}
```
### Full Width
```tsx
import {Label, SearchField} from "@heroui/react";
export function FullWidth() {
return (
Search
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
import {FieldError, Label, SearchField} from "@heroui/react";
export function Validation() {
return (
Search
Search query must be at least 3 characters
Search
Invalid characters in search query
);
}
```
### Controlled
Control the value to synchronize with other components or perform custom formatting.
```tsx
"use client";
import {Button, Description, Label, SearchField} from "@heroui/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
);
}
```
### Form Example
Complete form integration with validation and submission handling.
```tsx
"use client";
import {Button, Description, FieldError, Form, Label, SearchField, Spinner} from "@heroui/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.SyntheticEvent) => {
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 Validation
Implement custom validation logic with controlled values.
```tsx
"use client";
import {Description, FieldError, Label, SearchField} from "@heroui/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 "@heroui/react";
export function CustomIcons() {
return (
Search (Custom Icons)
Custom icon children
);
}
```
### With Keyboard Shortcut
Add keyboard shortcuts to quickly focus the search field.
```tsx
"use client";
import {Description, Kbd, Label, SearchField} from "@heroui/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
);
}
```
### Render Function
```tsx
"use client";
import {Label, SearchField} from "@heroui/react";
export function RenderFunction() {
return (
}>
Search
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Label, SearchField} from "@heroui/react";
export function CustomStyles() {
return (
Search docs
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
#### Base Classes \[!toc]
* `.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
#### Variant Classes \[!toc]
* `.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/react/components/label), [Description](/docs/react/components/description), [FieldError](/docs/react/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
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 `@heroui/react`
* **Description** - Helper text component from `@heroui/react`
* **FieldError** - Validation error message from `@heroui/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). |
### Render Props
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. |
## Related Showcases
## Related Components
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **FieldError**: Inline validation messages for form fields
# TextArea
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/text-area
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/text-area.mdx
> Primitive multiline text input component that accepts standard HTML attributes
## Usage
```tsx
import { TextArea } from '@heroui/react';
```
```tsx
import {TextArea} from "@heroui/react";
export function Basic() {
return (
);
}
```
For validation, labels, and error messages, see **[TextField](/docs/react/components/text-field)**.
## Examples
### 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 "@heroui/react";
export function Variants() {
return (
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {Surface, TextArea} from "@heroui/react";
export function OnSurface() {
return (
);
}
```
### Full Width
```tsx
import {TextArea} from "@heroui/react";
export function FullWidth() {
return (
);
}
```
### Controlled
```tsx
"use client";
import {Description, TextArea} from "@heroui/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 "@heroui/react";
export function Rows() {
return (
Short feedback
Detailed notes
);
}
```
## Customization
### Tailwind CSS
```tsx
import {TextArea} from "@heroui/react";
const fieldClass =
"rounded-xl border border-border/80 bg-surface shadow-sm ring-1 ring-black/5 transition-[box-shadow,border-color] focus-visible:ring-2 focus-visible:ring-neutral-400/25 dark:ring-white/10 dark:focus-visible:ring-neutral-500/30";
export function CustomStyles() {
return (
);
}
```
### Global CSS
Override the shared `.textarea` class once with Tailwind's `@layer components`.
```css
@layer components {
.textarea {
@apply rounded-xl border border-border bg-surface 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-accent ring-2 ring-accent/20;
}
&[data-invalid="true"] {
@apply border-danger bg-danger-soft text-danger;
}
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
#### Base Classes \[!toc]
* `.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
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/react/components/text-field)** with TextArea as a child component.
## Related Showcases
## Related Components
## 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
# TextField
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/text-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(forms)/text-field.mdx
> Composition-friendly text fields with labels, descriptions, and inline validation
## Usage
```tsx
import { TextField } from '@heroui/react';
```
```tsx
import {Input, Label, TextField} from "@heroui/react";
export function Basic() {
return (
Email
);
}
```
## Anatomy
```tsx
import {TextField, Label, Input, Description, FieldError} from '@heroui/react';
export default () => (
)
```
> **TextField** combines label, input, description, and error into a single accessible component.
> For standalone inputs, use **[Input](/docs/react/components/input)** or **[TextArea](/docs/react/components/text-area)**.
## Examples
### In Surface
When used inside a [Surface](/docs/react/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 "@heroui/react";
export function OnSurface() {
return (
Your name
We'll never share this with anyone else
Email
Bio
Minimum 4 rows
);
}
```
### With Description
```tsx
import {Description, Input, Label, TextField} from "@heroui/react";
export function WithDescription() {
return (
Username
Choose a unique username for your account
);
}
```
### Required Field
```tsx
import {Description, Input, Label, TextField} from "@heroui/react";
export function Required() {
return (
Full Name
This field is required
);
}
```
### Disabled State
```tsx
import {Description, Input, Label, TextField} from "@heroui/react";
export function Disabled() {
return (
Account ID
This field cannot be edited
);
}
```
### Full Width
```tsx
import {FieldError, Input, Label, TextField} from "@heroui/react";
export function FullWidth() {
return (
Your name
Password
Password must be longer than 8 characters
);
}
```
### Validation
Use `isInvalid` together with `FieldError` to surface validation messages.
```tsx
"use client";
import {Description, FieldError, Input, Label, TextArea, TextField} from "@heroui/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 "@heroui/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 "@heroui/react";
export function WithError() {
return (
Email
Please enter a valid email address
);
}
```
### TextArea
Use [TextArea](/docs/react/components/text-area) instead of [Input](/docs/react/components/input) for multiline content.
```tsx
import {Description, Label, TextArea, TextField} from "@heroui/react";
export function TextAreaExample() {
return (
Message
Maximum 500 characters
);
}
```
### Input Types
```tsx
import {Input, Label, TextField} from "@heroui/react";
export function InputTypes() {
return (
Password
Age
Email
Website
Phone
);
}
```
### Render Function
```tsx
"use client";
import {Input, Label, TextField} from "@heroui/react";
export function RenderFunction() {
return (
}
type="email"
>
Email
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Input, Label, TextField} from "@heroui/react";
const fieldClass =
"rounded-xl border border-border/80 bg-surface shadow-sm ring-1 ring-black/5 transition-[box-shadow,border-color] focus-visible:ring-2 focus-visible:ring-neutral-400/25 dark:ring-white/10 dark:focus-visible:ring-neutral-500/30";
export function CustomStyles() {
return (
Email
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
* `.textfield` – Root container with minimal styling (`flex flex-col gap-1`)
> **Note:** Child components ([Label](/docs/react/components/label), [Input](/docs/react/components/input), [TextArea](/docs/react/components/text-area), [Description](/docs/react/components/description), [FieldError](/docs/react/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
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 `@heroui/react`
* **Input** - Single-line text input from `@heroui/react`
* **TextArea** - Multi-line text input from `@heroui/react`
* **Description** - Helper text component from `@heroui/react`
* **FieldError** - Validation error message from `@heroui/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.
```
### Render Props
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). |
## Related Showcases
## Related Components
## 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
# Card
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/card
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(layout)/card.mdx
> Flexible container component for grouping related content and actions
## Usage
```tsx
import { Card } from "@heroui/react";
```
```tsx
import {CircleDollar} from "@gravity-ui/icons";
import {Card, Link} from "@heroui/react";
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
```tsx
import { Card } from "@heroui/react";
export default () => (
);
```
## Examples
### 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 "@heroui/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 "@heroui/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 "@heroui/react";
export function WithAvatar() {
return (
Indie Hackers
148 members
IH
By Martha
AI Builders
362 members
B
By John
);
}
```
### With Images
```tsx
import {CircleDollar} from "@gravity-ui/icons";
import {Avatar, Button, Card, CloseButton, Link} from "@heroui/react";
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 "@heroui/react";
export function WithForm() {
const onSubmit = (e: React.SyntheticEvent) => {
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?
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Check, Star} from "@gravity-ui/icons";
import {Button, Card} from "@heroui/react";
const PRO_FEATURES = [
"Unlimited projects and collaborators",
"Priority support with 24h response",
"Advanced analytics and exports",
] as const;
export function CustomStyles() {
return (
Recommended
Upgrade to Pro
Unlock team workflows and insights built for growing products.
{PRO_FEATURES.map((feature) => (
{feature}
))}
Upgrade now
Compare plans
);
}
```
### Global CSS
To customize the Card component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.card--secondary {
@apply bg-gradient-to-br from-blue-50 to-purple-50;
}
.card__title {
@apply text-xl font-bold;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
Card uses [BEM](https://getbem.com/) naming for predictable styling ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/card.css)):
#### Base Classes \[!toc]
* `.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 \[!toc]
* `.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 |
## Accessibility
```tsx
import { Card } from '@heroui/react';
import { cardVariants } from '@heroui/styles';
// Semantic markup
Article Title
// Interactive cards
Product Name
```
## Related Components
## Related Components
* **Surface**: Base container surface
* **Avatar**: Display user profile images
* **Form**: Form validation and submission handling
# Separator
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/separator
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(layout)/separator.mdx
> Visually divide content sections
## Usage
```tsx
import { Separator } from '@heroui/react';
```
```tsx
import {Separator} from "@heroui/react";
export function Basic() {
return (
HeroUI v3 Components
Beautiful, fast and modern React UI library.
);
}
```
## Examples
### Variants
```tsx
import {Separator} from "@heroui/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 "@heroui/react";
export function WithSurface() {
return (
Default Surface
Surface Content
Secondary Surface
Surface Content
Tertiary Surface
Surface Content
Transparent Surface
Surface Content
);
}
```
### Vertical
```tsx
import {Separator} from "@heroui/react";
export function Vertical() {
return (
);
}
```
### With Content
```tsx
import {Separator} from "@heroui/react";
const items = [
{
iconUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/bell-small.png",
subtitle: "Receive account activity updates",
title: "Set Up Notifications",
},
{
iconUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/compass-small.png",
subtitle: "Connect your browser to your account",
title: "Set up Browser Extension",
},
{
iconUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/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 &&
}
))}
);
}
```
### Decorative
Use `isDecorative` when the divider is purely visual, such as between listbox sections. It renders an `aria-hidden` div by default with presentational semantics, while preserving its visual styles. Custom render functions receive the decorative props and ref.
```tsx
```
### Render Function
```tsx
"use client";
import {Separator} from "@heroui/react";
export function RenderFunction() {
return (
HeroUI v3 Components
Beautiful, fast and modern React UI library.
} />
Blog
}
/>
Docs
}
/>
Source
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Separator} from "@heroui/react";
export function CustomStyles() {
return (
Account settings
Profile
Billing
Security
);
}
```
### Global CSS
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];
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/separator.css)):
#### Base & Orientation Classes \[!toc]
* `.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 \[!toc]
* `.separator--default` - Default variant with standard contrast
* `.separator--secondary` - Secondary variant with medium contrast
* `.separator--tertiary` - Tertiary variant with subtle contrast
## API Reference
### Separator
| Prop | Type | Default | Description |
| -------------- | ----------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- |
| `isDecorative` | `boolean` | `false` | Hides a purely visual divider from assistive technology and removes separator semantics |
| `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. |
## Related Components
## Related Components
* **Card**: Content container with header, body, and footer
* **Chip**: Compact elements for tags and filters
* **Avatar**: Display user profile images
# Surface
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/surface
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(layout)/surface.mdx
> Container component that provides surface-level styling and context for child components
## Usage
```tsx
import { Surface } from '@heroui/react';
```
```tsx
import {Surface} from "@heroui/react";
export function Basic() {
return (
Surface Content
This is a default surface variant. It uses bg-surface styling.
);
}
```
## Examples
### 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)
```tsx
import {Surface} from "@heroui/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.
);
}
```
### 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
"use client";
import {Input, Surface, TextArea} from "@heroui/react";
export function WithFormComponents() {
return (
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Surface} from "@heroui/react";
export function CustomStyles() {
return (
Billing overview
View invoices and payment methods in one place.
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/surface.css)):
#### Base Classes \[!toc]
* `.surface` - Base surface container
#### Variant Classes \[!toc]
* `.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
| 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 '@heroui/react';
function MyComponent() {
const { variant } = useContext(SurfaceContext);
// variant will be "transparent" | "default" | "secondary" | "tertiary" | undefined
}
```
## Related Components
## Related Components
* **CheckboxGroup**: Group of checkboxes with shared state
* **Fieldset**: Group related form controls with legends
* **InputOTP**: One-time password input
# Toolbar
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/toolbar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(layout)/toolbar.mdx
> A container for interactive controls with arrow key navigation.
## Usage
```tsx
import { Toolbar } from '@heroui/react';
```
```tsx
import {Bold, Copy, Italic, Scissors, Underline} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function Basic() {
return (
);
}
```
## Examples
### Vertical
```tsx
import {ArrowUturnCcwLeft, ArrowUturnCwRight, Bold, Italic, Underline} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function Vertical() {
return (
);
}
```
### Attached
```tsx
import {Bold, Copy, Italic, Scissors, Underline} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function Attached() {
return (
);
}
```
### With ButtonGroup
```tsx
import {
ArrowUturnCcwLeft,
ArrowUturnCwRight,
Bold,
Italic,
TextAlignCenter,
TextAlignLeft,
TextAlignRight,
Underline,
} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function WithButtonGroup() {
return (
Undo
Redo
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Bold, Italic, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup, Toolbar} from "@heroui/react";
const toggleClass =
"rounded-lg data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground";
export function CustomStyles() {
return (
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/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
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 |
### Render Props
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ------------- | ---------------------------- | -------------------------------------- |
| `orientation` | `"horizontal" \| "vertical"` | The current orientation of the toolbar |
## Related Components
## Related Components
* **ButtonGroup**: Group related buttons together
* **ToggleButtonGroup**: Group multiple toggle buttons into a unified control
* **Separator**: Visual divider between content
# AvatarGroup
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/avatar-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(media)/avatar-group.mdx
> Display a stacked or grid group of avatars with overflow counting
## Usage
```tsx
import { AvatarGroup, Avatar } from '@heroui/react';
```
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "John Doe",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "Kate Wilson",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Emily Chen",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "Michael Brown",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "Olivia Davis",
},
];
export function Basic() {
return (
{users.slice(0, 4).map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
);
}
```
## Anatomy
```tsx
import { AvatarGroup, Avatar } from '@heroui/react';
export default () => (
{/* Optional explicit child */}
);
```
## Examples
### Max
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "John Doe",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "Kate Wilson",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Emily Chen",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "Michael Brown",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "Olivia Davis",
},
];
export function Max() {
return (
{users.map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
);
}
```
### With Count
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "John Doe",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "Kate Wilson",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Emily Chen",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "Michael Brown",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "Olivia Davis",
},
];
export function Count() {
const total = 12;
return (
{users.slice(0, 3).map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
+{total - 3}
);
}
```
### Sizes
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "John Doe",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "Kate Wilson",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Emily Chen",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "Michael Brown",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "Olivia Davis",
},
];
export function Sizes() {
return (
Small
{users.slice(0, 4).map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
Medium (default)
{users.slice(0, 4).map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
Large
{users.slice(0, 4).map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
);
}
```
### Grid
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "John Doe",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "Kate Wilson",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Emily Chen",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "Michael Brown",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "Olivia Davis",
},
];
export function Grid() {
return (
{users.map((user) => (
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
))}
);
}
```
### Overlap
```tsx
import type {ReactNode} from "react";
import {Avatar, AvatarGroup} from "@heroui/react";
const STRIPE_STYLES = `
@keyframes ag-stripes {
to { background-position: 24px 24px; }
}
.ag-stripes {
background-color: color-mix(in oklab, var(--danger) 6%, var(--surface));
background-image: repeating-linear-gradient(
-45deg,
transparent 0 10px,
color-mix(in oklab, var(--danger) 12%, transparent) 10px 11px,
transparent 11px 21px,
color-mix(in oklab, var(--danger) 28%, transparent) 21px 22px
);
background-size: 24px 24px;
animation: ag-stripes 2.8s linear infinite;
}
`;
function StripeBackdrop({children}: {children: ReactNode}) {
return (
);
}
function DemoGroup({overlap}: {overlap: "clip" | "ring"}) {
return (
JD
AB
EC
SM
+2
);
}
export function Overlap() {
return (
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Person} from "@gravity-ui/icons";
import {Avatar, AvatarGroup} from "@heroui/react";
const assignees = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "John Doe",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "Kate Wilson",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Emily Chen",
},
] as const;
export function CustomStyles() {
return (
{assignees.map((user) => (
{user.name
.split(" ")
.map((part) => part[0])
.join("")}
))}
+3
Assignees
);
}
```
### Global CSS
To customize the AvatarGroup 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-group {
--avatar-group-overlap: 0.75rem;
--avatar-group-seam: 2px;
}
.avatar-group__count {
@apply font-semibold;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The AvatarGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/avatar-group.css)):
#### Base Classes \[!toc]
* `.avatar-group` - Base avatar group container
* `.avatar-group--grid` - Grid layout modifier (no overlap)
* `.avatar-group--clip` - Transparent seam tracing the next avatar's outline (default stacked overlap)
* `.avatar-group--ring` - Solid box-shadow outline via `--background`
* `.avatar-group__count` - Overflow count avatar
Stacked layout uses negative margin between siblings (`--avatar-group-overlap`, default `0.5rem`). Clip mode masks overlapped avatars (not the last child / Count) and optically nudges fallback glyphs; the seam follows the avatar's own `--avatar-radius`, so a themed `--radius` reshapes avatars and seams together. Ring mode uses a thin box-shadow outline via `--background`.
## API Reference
### AvatarGroup
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size applied to child Avatars (and the overflow count) when they omit `size` (synced with Avatar) |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | - | Color applied to child Avatars (and the overflow count) when they omit `color` |
| `variant` | `'default' \| 'soft'` | - | Variant applied to child Avatars (and the overflow count) when they omit `variant` |
| `max` | `number` | - | Maximum number of avatar children to render; omit to show all |
| `isGrid` | `boolean` | `false` | Whether to use a wrapping grid layout without overlap |
| `overlap` | `'clip' \| 'ring'` | `'clip'` | Stacked overlap style: transparent seam (default) or box-shadow ring; ignored when `isGrid` |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Avatar components (and optional `AvatarGroup.Count`) to group together |
### AvatarGroup.Count
Composes `Avatar` + `Avatar.Fallback` for the overflow indicator. Not truncated by `max`.
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | ------- | ------------------------------- |
| `children` | `React.ReactNode` | - | Count content (e.g. `+3`) |
| `size` | `'sm' \| 'md' \| 'lg'` | - | Override size from the group |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | - | Override color from the group |
| `variant` | `'default' \| 'soft'` | - | Override variant from the group |
| `className` | `string` | - | Additional CSS classes |
## Note
* AvatarGroup uses React Context to pass `size`, `color`, and `variant` to **direct Avatar children only**
* `max` truncates visible avatars when set and auto-counts remaining children; omit to show all
* For a known total (e.g. from the server), render an explicit `AvatarGroup.Count` — it is not truncated by `max` and suppresses the auto count. Prefer `Count` over the removed v2 `total` prop
* `isGrid` disables overlap and uses a wrapping grid layout instead
* `overlap` defaults to `"clip"` (transparent seam); use `"ring"` for the solid outline; no effect with `isGrid`
* No default `role="group"` — when the stack is meaningful (assignees, viewers), pass `role="group"` with `aria-label` or `aria-labelledby`
## Related Components
## Related Components
* **Avatar**: Display user profile images
* **Badge**: Small indicator positioned relative to another element
* **Chip**: Compact elements for tags and filters
# Avatar
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/avatar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(media)/avatar.mdx
> Display user profile images with customizable fallback content
## Usage
```tsx
import { Avatar } from '@heroui/react';
```
```tsx
import {Avatar} from "@heroui/react";
export function Basic() {
return (
);
}
```
## Anatomy
```tsx
import { Avatar } from '@heroui/react';
export default () => (
)
```
## Examples
### Sizes
```tsx
import {Avatar} from "@heroui/react";
export function Sizes() {
return (
);
}
```
### Colors
```tsx
import {Avatar} from "@heroui/react";
export function Colors() {
return (
);
}
```
### Variants
```tsx
import {Person} from "@gravity-ui/icons";
import {Avatar, Separator} from "@heroui/react";
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://img.heroui.chat/image/avatar?w=400&h=400&u=3",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=4",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=5",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=8",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=16",
],
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 {Person} from "@gravity-ui/icons";
import {Avatar} from "@heroui/react";
export function Fallback() {
return (
{/* Text fallback */}
JD
{/* Icon fallback */}
{/* Fallback with delay */}
NA
{/* Custom styled fallback */}
GB
);
}
```
## Customization
### Custom Image Component
Use `asChild` on `Avatar.Image` to compose with a custom image component. This example uses Next.js `Image` for optimized loading. Pass `src` on `Avatar.Image` so it can track the loading state and show the fallback until the image is ready.
```tsx
import {Avatar} from "@heroui/react";
import Image from "next/image";
const SRC = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg";
export function CustomImageComponent() {
return (
JD
);
}
```
### Tailwind CSS
```tsx
import {Avatar} from "@heroui/react";
export function CustomStyles() {
return (
JD
);
}
```
### Global CSS
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-accent;
}
.avatar__fallback {
@apply bg-gradient-to-br from-purple-500 to-pink-500;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/avatar.css)):
#### Base Classes \[!toc]
* `.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 \[!toc]
* `.avatar--sm` - Small avatar (size-8)
* `.avatar--md` - Medium avatar (default, no additional styles)
* `.avatar--lg` - Large avatar (size-12)
#### Variant Modifiers \[!toc]
* `.avatar--soft` - Soft variant with lighter background
#### Color Modifiers \[!toc]
* `.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
| 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
| 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 |
| `asChild` | `boolean` | `false` | Merge props onto the child element (e.g. `next/image`) instead of rendering a native `img` |
| `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
| 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 |
## Related Components
## Related Components
* **AvatarGroup**: Display a stacked or grid group of avatars
* **Separator**: Visual divider between content
* **Badge**: Small indicator positioned relative to another element
# Accordion
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/accordion
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(navigation)/accordion.mdx
> A collapsible content panel for organizing information in a compact space
## Usage
```tsx
import { Accordion } from '@heroui/react';
```
```tsx
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
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
```tsx
import { Accordion } from '@heroui/react';
export default () => (
)
```
## Examples
### Surface
```tsx
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
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}
))}
);
}
```
### Without Separator
```tsx
import {ChevronDown, CreditCard, Receipt, ShoppingBag} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
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}
))}
);
}
```
### Multiple Expanded
```tsx
import {Accordion} from "@heroui/react";
export function Multiple() {
return (
Getting Started
Learn the basics of HeroUI 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 HeroUI, 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 HeroUI components.
);
}
```
### Disabled State
```tsx
import {Accordion} from "@heroui/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.
);
}
```
### Controlled
```tsx
"use client";
import {ChevronDown, ChevronUp} from "@gravity-ui/icons";
import {Accordion, Button, useDisclosureGroupNavigation} from "@heroui/react";
import React from "react";
const items = [
{
content:
"Learn the basics of HeroUI 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 HeroUI, 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 "@heroui/react";
import {ChevronsDown, CircleChevronDown, Minus, Plus} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
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.
);
}
```
### Render Function
```tsx
"use client";
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
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 RenderFunction() {
return (
}
>
{items.map((item, index) => (
}>
}>
}>
{item.icon ? (
{item.icon}
) : null}
{item.title}
}>
{item.content}
))}
);
}
```
### FAQ Layout
```tsx
import {ChevronDown} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
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}
))}
))}
);
}
```
## Customization
### Tailwind CSS
```tsx
import {ChevronDown} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
const items = [
{
content: "Stay informed about your account activity with real-time notifications.",
iconUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/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://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/compass-small.png",
subtitle: "Connect your 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://heroui-assets.nyc3.cdn.digitaloceanspaces.com/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}
))}
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/accordion.css)):
#### Base Classes \[!toc]
* `.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 \[!toc]
* `.accordion--outline` - Outline variant with border and background
#### State Classes \[!toc]
* `.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
| 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
| 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
| 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
| 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
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator icon |
### Accordion.Body
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Body content |
## Related Components
## Related Components
* **DisclosureGroup**: Group of collapsible panels
* **Disclosure**: Single collapsible content section
# Breadcrumbs
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/breadcrumbs
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(navigation)/breadcrumbs.mdx
> Navigation breadcrumbs showing the current page's location within a hierarchy
## Usage
```tsx
import { Breadcrumbs } from '@heroui/react';
```
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsBasic() {
return (
Home
Products
Electronics
Laptop
);
}
```
## Anatomy
```tsx
import { Breadcrumbs } from '@heroui/react';
export default () => (
Home
Category
Current Page
)
```
## Examples
### Navigation Levels
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsLevel2() {
return (
Home
Current Page
);
}
```
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsLevel3() {
return (
Home
Category
Current Page
);
}
```
### Disabled State
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsDisabled() {
return (
Home
Products
Electronics
Laptop
);
}
```
### Custom Separator
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsCustomSeparator() {
return (
}
>
Home
Products
Electronics
Laptop
);
}
```
### Render Function
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export function RenderFunction() {
return (
}>
}>
Home
}>
Products
}>
Electronics
}>
Laptop
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export function CustomStyles() {
return (
Home
Products
Laptop
);
}
```
### Global CSS
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;
}
}
```
## Styling Reference
HeroUI 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/heroui-inc/heroui/blob/v3/packages/styles/components/breadcrumbs.css)):
#### Base Classes \[!toc]
* `.breadcrumbs` - Base breadcrumbs container
* `.breadcrumbs__item` - Individual breadcrumb item wrapper
* `.breadcrumbs__link` - Breadcrumb link element
* `.breadcrumbs__separator` - Separator icon between items
#### State Classes \[!toc]
* `.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
| 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
| 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.
## Related Components
## Related Components
* **Link**: Styled anchor links
* **Separator**: Visual divider between content
* **Tabs**: Organize content into tabbed views
# DisclosureGroup
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/disclosure-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(navigation)/disclosure-group.mdx
> Container that manages multiple Disclosure items with coordinated expanded states
## Usage
```tsx
import { DisclosureGroup } from '@heroui/react';
```
```tsx
"use client";
import {QrCode} from "@gravity-ui/icons";
import {Button, Disclosure, DisclosureGroup, Separator} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
import {cn} from "tailwind-variants";
export function Basic() {
const [expandedKeys, setExpandedKeys] = React.useState(new Set(["preview"]));
return (
Preview HeroUI Native
Scan this QR code with your camera app to preview the HeroUI native components.
Expo must be installed on your device.
Preview on Expo Go
Download App
Download the HeroUI native app to explore our mobile components directly on your
device.
Available on iOS and Android devices.
Download on App Store
);
}
```
## Anatomy
```tsx
import {DisclosureGroup, Disclosure} from '@heroui/react';
export default () => (
)
```
## Examples
### Controlled
You can control which disclosures are expanded with external navigation controls using the `expandedKeys` and `onExpandedChange` props.
```tsx
"use client";
import {ChevronDown, ChevronUp, QrCode} from "@gravity-ui/icons";
import {
Button,
Disclosure,
DisclosureGroup,
Separator,
useDisclosureGroupNavigation,
} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
import {cn} from "tailwind-variants";
export function Controlled() {
const [expandedKeys, setExpandedKeys] = React.useState(new Set(["preview"]));
const itemIds = ["preview", "download"]; // Track our disclosure items
const {isNextDisabled, isPrevDisabled, onNext, onPrevious} = useDisclosureGroupNavigation({
expandedKeys,
itemIds,
onExpandedChange: setExpandedKeys,
});
return (
Preview HeroUI Native
Scan this QR code with your camera app to preview the HeroUI native components.
Expo must be installed on your device.
Preview on Expo Go
Download HeroUI Native
Scan this QR code with your camera app to preview the HeroUI native components.
Expo must be installed on your device.
Download on App Store
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {Button, Disclosure, DisclosureGroup, Separator} from "@heroui/react";
export function CustomStyles() {
return (
Billing
Invoices are issued on the first of each month.
Support
Reach us at help@heroui.com. Typical response time is under one business day.
);
}
```
### Global CSS
To customize the DisclosureGroup component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.disclosure-group {
@apply w-full;
/* Performance optimization */
contain: layout style;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The DisclosureGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/disclosure-group.css)):
#### Base Classes \[!toc]
* `.disclosure-group` - Base container styles with layout containment
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
* **Disabled**: `:disabled` or `[aria-disabled="true"]` on entire group
* **Expanded Management**: Automatically manages `[data-expanded]` states on child Disclosure items
## API Reference
### DisclosureGroup
| Prop | Type | Default | Description |
| ------------------------ | ----------------------------- | ------- | ----------------------------------------------------- |
| `expandedKeys` | `Set` | - | The currently expanded items (controlled) |
| `defaultExpandedKeys` | `Iterable` | - | The initially expanded items (uncontrolled) |
| `onExpandedChange` | `(keys: Set) => void` | - | Handler called when expanded items change |
| `allowsMultipleExpanded` | `boolean` | `false` | Whether multiple items can be expanded simultaneously |
| `isDisabled` | `boolean` | `false` | Whether all disclosures in the group are disabled |
| `children` | `ReactNode \| RenderFunction` | - | Disclosure items to render |
| `className` | `string` | - | Additional CSS classes |
### Render Props
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| -------------- | ---------- | ----------------------------- |
| `expandedKeys` | `Set` | Currently expanded item keys |
| `isDisabled` | `boolean` | Whether the group is disabled |
## Related Showcases
## Related Components
## Related Components
* **Accordion**: Collapsible content sections
* **Disclosure**: Single collapsible content section
* **Button**: Allows a user to perform an action
# Disclosure
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/disclosure
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(navigation)/disclosure.mdx
> A disclosure is a collapsible section with a header containing a heading and a trigger button, and a panel that wraps the content.
## Usage
```tsx
import { Disclosure } from '@heroui/react';
```
```tsx
"use client";
import {QrCode} from "@gravity-ui/icons";
import {Button, Disclosure} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function Basic() {
const [isExpanded, setIsExpanded] = React.useState(true);
return (
Preview HeroUI Native
Scan this QR code with your camera app to preview the HeroUI native components.
Expo must be installed on your device.
Download on App Store
);
}
```
## Anatomy
```tsx
import { Disclosure } from '@heroui/react';
export default () => (
)
```
## Examples
### Render Function
```tsx
"use client";
import {QrCode} from "@gravity-ui/icons";
import {Button, Disclosure} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function RenderFunction() {
const [isExpanded, setIsExpanded] = React.useState(true);
return (
}
onExpandedChange={setIsExpanded}
>
Preview HeroUI Native
}>
Scan this QR code with your camera app to preview the HeroUI native components.
Expo must be installed on your device.
Download on App Store
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {ShoppingBag} from "@gravity-ui/icons";
import {Button, Disclosure} from "@heroui/react";
import {useState} from "react";
export function CustomStyles() {
const [isExpanded, setIsExpanded] = useState(false);
return (
Shipping details
Orders ship within 2 business days. Standard delivery takes 3–5 days; express is
available at checkout.
);
}
```
### Global CSS
To customize the Disclosure component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.disclosure {
@apply relative;
}
.disclosure__trigger {
@apply cursor-pointer;
}
.disclosure__indicator {
@apply transition-transform duration-300;
}
.disclosure__content {
@apply overflow-hidden transition-all;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Disclosure component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/disclosure.css)):
#### Base Classes \[!toc]
* `.disclosure` - Base container styles
* `.disclosure__heading` - Heading wrapper
* `.disclosure__trigger` - Trigger button styles
* `.disclosure__indicator` - Chevron indicator styles
* `.disclosure__content` - Content container with animations
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
* **Expanded**: `[data-expanded="true"]` on indicator for rotation
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on trigger
* **Disabled**: `:disabled` or `[aria-disabled="true"]` on trigger
* **Hidden**: `[aria-hidden="false"]` on content for visibility
## API Reference
### Disclosure
| Prop | Type | Default | Description |
| ------------------ | ----------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `isExpanded` | `boolean` | `false` | Controls the expanded state |
| `onExpandedChange` | `(isExpanded: boolean) => void` | - | Callback when expanded state changes |
| `isDisabled` | `boolean` | `false` | Whether the disclosure is disabled |
| `children` | `ReactNode \| RenderFunction` | - | Content to render |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### DisclosureTrigger
| Prop | Type | Default | Description |
| ----------- | ----------------------------- | ------- | ---------------------- |
| `children` | `ReactNode \| RenderFunction` | - | Trigger content |
| `className` | `string` | - | Additional CSS classes |
### DisclosureContent
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------- |
| `children` | `ReactNode` | - | Content to show/hide |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Render Props
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
| ------------ | --------- | ------------------------------ |
| `isExpanded` | `boolean` | Current expanded state |
| `isDisabled` | `boolean` | Whether disclosure is disabled |
## Related Showcases
## Related Components
## Related Components
* **Accordion**: Collapsible content sections
* **DisclosureGroup**: Group of collapsible panels
* **Button**: Allows a user to perform an action
# Link
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/link
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(navigation)/link.mdx
> A styled anchor component for navigation with built-in icon support
## Usage
```tsx
import { Link } from '@heroui/react';
```
```tsx
import {Link} from "@heroui/react";
export function LinkBasic() {
return (
Call to action
);
}
```
## Anatomy
```tsx
import { Link } from '@heroui/react';
export default () => (
Call to action
);
```
## Examples
### Icon Placement
```tsx
import {Link} from "@heroui/react";
export function LinkIconPlacement() {
return (
Icon at end (default)
Icon at start
);
}
```
### Text Decoration with Tailwind CSS
Link is underlined on hover by default. Use Tailwind CSS text-decoration utilities to make the underline always visible, remove it entirely, or customize its color, style, thickness, and offset.
```tsx
import {Link} from "@heroui/react";
export function LinkUnderlineAndOffset() {
return (
Default hover underline
Hover to see the underline
Always visible underline
Underline always visible
No underline
Link without any underline
Changing the underline offset
Offset 1 (1px space)
Offset 2 (2px space)
Offset 3 (3px space)
Offset 4 (4px space)
);
}
```
**Text Decoration Line:**
* `underline` - Always visible underline
* `no-underline` - Remove underline
* default `Link` styles - Underline appears on hover
**Text Decoration Color:**
* `decoration-accent`, `decoration-muted`, etc. - Set underline color using theme colors
* `decoration-muted/50` - Use opacity modifiers for semi-transparent underlines
**Text Decoration Style:**
* `decoration-solid` - Solid line (default)
* `decoration-double` - Double line
* `decoration-dotted` - Dotted line
* `decoration-dashed` - Dashed line
* `decoration-wavy` - Wavy line
**Text Decoration Thickness:**
* `decoration-1`, `decoration-2`, `decoration-4`, etc. - Control underline thickness
**Underline Offset:**
* `underline-offset-1`, `underline-offset-2`, `underline-offset-4`, etc. - Adjust spacing between text and underline
For more details, see the Tailwind CSS documentation:
* [text-decoration-line](https://tailwindcss.com/docs/text-decoration-line)
* [text-decoration-color](https://tailwindcss.com/docs/text-decoration-color)
* [text-decoration-style](https://tailwindcss.com/docs/text-decoration-style)
* [text-decoration-thickness](https://tailwindcss.com/docs/text-decoration-thickness)
* [text-underline-offset](https://tailwindcss.com/docs/text-underline-offset)
Available BEM classes:
* Base: `link`
* Icon: `link__icon`
### Custom Icon
```tsx
import {ArrowUpRightFromSquare, Link as LinkIcon} from "@gravity-ui/icons";
import {Link} from "@heroui/react";
export function LinkCustomIcon() {
return (
);
}
```
### Render Function
```tsx
"use client";
import {Link} from "@heroui/react";
export function RenderFunction() {
return (
}>
Call to action
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Link} from "@heroui/react";
export function CustomStyles() {
return (
Call to action
);
}
```
### Global CSS
To customize the Link component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.link {
@apply font-semibold no-underline hover:underline;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Link component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/link.css)):
#### Base Classes \[!toc]
* `.link` - Base link styles
* `.link__icon` - Link icon styles
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]`
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Pressed**: `:active` or `[data-pressed="true"]`
* **Disabled**: `:disabled` or `[aria-disabled="true"]`
## API Reference
### Link
| Prop | Type | Default | Description |
| ------------ | ----------------------------------------------------------------------- | --------- | ---------------------------------------------------------------- |
| `href` | `string` | - | Destination URL for the anchor |
| `target` | `string` | `"_self"` | Controls where to open the linked document |
| `rel` | `string` | - | Relationship between the current and linked documents |
| `download` | `boolean \| string` | - | Prompts file download instead of navigation |
| `isDisabled` | `boolean` | `false` | Disables pointer and keyboard interaction |
| `className` | `string` | - | Custom classes merged with the default styles |
| `children` | `React.ReactNode` | - | Content rendered inside the link |
| `onPress` | `(e: PressEvent) => void` | - | Fired when the link is activated |
| `autoFocus` | `boolean` | - | Whether the element should receive focus on render |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Link.Icon
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | --------------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | Custom icon element; defaults to the built-in arrow icon when omitted |
| `className` | `string` | - | Additional CSS classes |
### Using with Routing Libraries
Use variant functions to style framework-specific links like Next.js:
```tsx
import { Link } from '@heroui/react';
import { linkVariants } from '@heroui/styles';
import NextLink from 'next/link';
export default function Demo() {
const slots = linkVariants();
return (
About Page
);
}
```
### Direct Class Application
Since HeroUI uses [BEM](https://getbem.com/) classes, you can apply Link styles directly to any link element:
```tsx
import NextLink from 'next/link';
// Apply classes directly with Tailwind utilities
export default function Demo() {
return (
About Page
);
}
// Or with a native anchor
export default function NativeLink() {
return (
About Page
);
}
```
## Related Components
## Related Components
* **Breadcrumbs**: Display the user's current location within a hierarchy
# Pagination
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/pagination
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(navigation)/pagination.mdx
> Page navigation with composable page links, previous/next buttons, and ellipsis indicators
## Usage
```tsx
import { Pagination } from '@heroui/react';
```
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationBasic() {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
setPage((p) => p - 1)}>
Previous
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
Next
);
}
```
## Anatomy
```tsx
import { Pagination } from '@heroui/react';
export default () => (
Showing 1-10 of 100 results
Previous
1
10
Next
);
```
## Examples
### Sizes
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
function SizePagination({size}: {size: "sm" | "md" | "lg"}) {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
{size}
setPage((p) => p - 1)}>
Previous
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
Next
);
}
export function PaginationSizes() {
return (
{(["sm", "md", "lg"] as const).map((size) => (
))}
);
}
```
### Disabled
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationDisabled() {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
setPage((p) => p - 1)}>
Previous
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
Next
);
}
```
### Simple (Previous / Next)
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationSimplePrevNext() {
const [page, setPage] = useState(1);
const totalPages = 10;
const itemsPerPage = 5;
const totalItems = 50;
const startItem = (page - 1) * itemsPerPage + 1;
const endItem = Math.min(page * itemsPerPage, totalItems);
return (
{startItem} to {endItem} of {totalItems} invoices
setPage((p) => p - 1)}>
Prev
setPage((p) => p + 1)}>
Next
);
}
```
### Controlled
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationControlled() {
const [page, setPage] = useState(1);
const totalPages = 12;
const itemsPerPage = 10;
const totalItems = 120;
const getPageNumbers = () => {
const pages: (number | "ellipsis")[] = [];
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
pages.push(1);
if (page > 3) {
pages.push("ellipsis");
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPages - 1, page + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (page < totalPages - 2) {
pages.push("ellipsis");
}
pages.push(totalPages);
}
return pages;
};
const startItem = (page - 1) * itemsPerPage + 1;
const endItem = Math.min(page * itemsPerPage, totalItems);
return (
Showing {startItem}-{endItem} of {totalItems} results
setPage((p) => p - 1)}>
Previous
{getPageNumbers().map((p, i) =>
p === "ellipsis" ? (
) : (
setPage(p)}>
{p}
),
)}
setPage((p) => p + 1)}>
Next
);
}
```
### With Ellipsis
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationWithEllipsis() {
const [page, setPage] = useState(1);
const totalPages = 12;
const getPageNumbers = () => {
const pages: (number | "ellipsis")[] = [];
pages.push(1);
if (page > 3) {
pages.push("ellipsis");
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPages - 1, page + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (page < totalPages - 2) {
pages.push("ellipsis");
}
pages.push(totalPages);
return pages;
};
return (
setPage((p) => p - 1)}>
Previous
{getPageNumbers().map((p, i) =>
p === "ellipsis" ? (
) : (
setPage(p)}>
{p}
),
)}
setPage((p) => p + 1)}>
Next
);
}
```
### With Summary
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationWithSummary() {
const [page, setPage] = useState(1);
const totalPages = 12;
const itemsPerPage = 10;
const totalItems = 120;
const getPageNumbers = () => {
const pages: (number | "ellipsis")[] = [];
pages.push(1);
if (page > 3) {
pages.push("ellipsis");
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPages - 1, page + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (page < totalPages - 2) {
pages.push("ellipsis");
}
pages.push(totalPages);
return pages;
};
const startItem = (page - 1) * itemsPerPage + 1;
const endItem = Math.min(page * itemsPerPage, totalItems);
return (
Showing {startItem}-{endItem} of {totalItems} results
setPage((p) => p - 1)}>
Previous
{getPageNumbers().map((p, i) =>
p === "ellipsis" ? (
) : (
setPage(p)}>
{p}
),
)}
setPage((p) => p + 1)}>
Next
);
}
```
### Custom Icons
You can replace the default chevron icons by passing custom children to `PreviousIcon` and `NextIcon`.
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function PaginationCustomIcons() {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
setPage((p) => p - 1)}>
Back
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
Forward
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
const linkClass = "text-muted hover:bg-surface hover:text-foreground";
const activeClass = "bg-accent text-accent-foreground hover:bg-accent-hover";
export function CustomStyles() {
const [page, setPage] = useState(2);
return (
setPage((p) => p - 1)}
>
{[1, 2, 3].map((p) => (
setPage(p)}
>
{p}
))}
setPage((p) => p + 1)}
>
);
}
```
### Global CSS
To customize the Pagination component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.pagination {
@apply gap-8;
}
.pagination__link {
@apply rounded-md;
}
.pagination__summary {
@apply text-xs font-semibold;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Pagination component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/pagination.css)):
#### Base & Layout Classes \[!toc]
* `.pagination` - Root navigation container with flex layout
* `.pagination__summary` - Left-side info text container
* `.pagination__content` - Container for pagination items
* `.pagination__item` - Individual item wrapper
* `.pagination__link` - Page number button (ghost button style)
* `.pagination__link--nav` - Navigation button modifier (Previous/Next)
* `.pagination__ellipsis` - Ellipsis indicator
#### Size Classes \[!toc]
* `.pagination--sm` - Small size variant
* `.pagination--md` - Medium size variant (default)
* `.pagination--lg` - Large size variant
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
* **Active page**: `[data-active="true"]` or `[aria-current="page"]`
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]`
* **Disabled**: `:disabled` or `[aria-disabled="true"]`
* **Pressed**: `:active` or `[data-pressed="true"]`
## API Reference
### Pagination
| Prop | Type | Default | Description |
| ----------- | ---------------------- | ------- | ----------------------------------- |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the pagination items |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Pagination parts (Summary, Content) |
### Pagination.Summary
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | --------------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Summary content (e.g., "Showing 1-10 of 120") |
### Pagination.Content
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Pagination items |
### Pagination.Item
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------------------------------------------ |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Item content (Link, Previous, Next, or Ellipsis) |
### Pagination.Link
| Prop | Type | Default | Description |
| ------------ | ------------------------- | ------- | -------------------------------- |
| `isActive` | `boolean` | `false` | Whether this is the current page |
| `isDisabled` | `boolean` | `false` | Whether the link is disabled |
| `onPress` | `(e: PressEvent) => void` | - | Press handler (from React Aria) |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Page number content |
### Pagination.Previous / Pagination.Next
| Prop | Type | Default | Description |
| ------------ | ------------------------- | ------- | --------------------------------------------------- |
| `isDisabled` | `boolean` | `false` | Whether the button is disabled |
| `onPress` | `(e: PressEvent) => void` | - | Press handler (from React Aria) |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Button content (compose with PreviousIcon/NextIcon) |
### Pagination.PreviousIcon / Pagination.NextIcon
| Prop | Type | Default | Description |
| ----------- | ----------- | ------------------- | ------------------------------------------ |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | Default chevron SVG | Custom icon to replace the default chevron |
### Pagination.Ellipsis
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
## Note
Pagination buttons use `onPress` instead of `onClick`. The `onPress` handler from React Aria normalizes press behavior across pointer types and provides accessibility improvements out of the box.
## Accessibility
The Pagination component is built on [React Aria's Button](https://react-spectrum.adobe.com/react-aria/Button.html) primitive for all interactive elements, providing:
* Semantic `` element with `aria-label="pagination"` and `role="navigation"`
* Active page indicated via `aria-current="page"` on the current link
* Keyboard navigation via Tab key through all interactive elements
* Press events handled across mouse, touch, and keyboard interactions via React Aria
* Focus ring on keyboard navigation via `:focus-visible`
* Ellipsis marked with `aria-hidden="true"` to avoid screen reader confusion
* Disabled states properly communicated to assistive technology via `isDisabled`
## Related Components
## Related Components
* **Button**: Allows a user to perform an action
* **Link**: Styled anchor links
# Tabs
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/tabs
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(navigation)/tabs.mdx
> Tabs organize content into multiple sections and allow users to navigate between them.
## Usage
```tsx
import { Tabs } from '@heroui/react';
```
## Anatomy
```tsx
import { Tabs } from '@heroui/react';
export default () => (
{/* Optional */}
)
```
## Examples
### Vertical
### Overflow
When the tab list exceeds the available space, `Tabs.ListContainer` automatically renders scroll chevrons and fading edges so users can navigate the hidden tabs. This works the same way for both horizontal and vertical orientations.
### Disabled Tab
### With Separator
Add ` ` inside each `` (except the first) to display separator lines between tabs.
### Secondary Variant
### Secondary Variant Vertical
### Alignment
Tab content is centered by default. Use `align` to align it to the `start` or `end` instead, which is
especially useful for vertical, sidebar-style navigation. Individual tabs can still be overridden with
`className` on `Tabs.Tab`.
### Render Function
```tsx
"use client";
import {Tabs} from "@heroui/react";
import Link from "next/link";
export function RenderFunction() {
return (
}>
}
>
Getting Started
}
>
Components
}
>
Releases
View your project overview and recent activity.
Track your metrics and analyze performance data.
Generate and download detailed reports.
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Tabs} from "@heroui/react";
const listClassName = [
"rounded-xl border border-accent/10 bg-accent-soft/30 p-1",
"**:data-[slot=tabs-tab]:rounded-lg",
"**:data-[slot=tabs-tab]:bg-transparent",
"**:data-[slot=tabs-tab]:text-muted",
"**:data-[slot=tabs-tab]:opacity-100",
"**:data-[slot=tabs-tab]:transition-colors",
"**:data-[slot=tabs-tab]:data-[hovered=true]:not-data-[selected=true]:bg-accent-soft",
"**:data-[slot=tabs-tab]:data-[hovered=true]:not-data-[selected=true]:text-accent-soft-foreground",
"**:data-[slot=tabs-tab]:data-[pressed=true]:not-data-[selected=true]:bg-accent-soft-hover",
"**:data-[slot=tabs-tab]:data-[focus-visible=true]:ring-2",
"**:data-[slot=tabs-tab]:data-[focus-visible=true]:ring-accent/15",
"**:data-[slot=tabs-tab]:data-[selected=true]:font-medium",
"**:data-[slot=tabs-tab]:data-[selected=true]:text-accent-foreground",
"**:data-[slot=tabs-tab]:shadow-none",
"**:data-[slot=tabs-indicator]:rounded-lg",
"**:data-[slot=tabs-indicator]:bg-accent",
"**:data-[slot=tabs-indicator]:shadow-none",
].join(" ");
export function CustomStyles() {
return (
Monthly
Yearly
Billed monthly, cancel anytime.
Save 20% with annual billing.
);
}
```
### Global CSS
To customize the Tabs component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.tabs__list {
@apply rounded-xl border border-border bg-surface p-1;
}
.tabs__tab {
@apply rounded-lg font-medium;
}
.tabs__indicator {
@apply rounded-lg bg-foreground shadow-sm;
}
.tabs--secondary > .tabs__list-container > .tabs__list {
@apply border-b border-border bg-transparent p-0;
}
.tabs--secondary > .tabs__list-container .tabs__indicator {
@apply rounded-none bg-accent;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Tabs component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tabs.css)):
#### Base Classes \[!toc]
* `.tabs` - Base tabs container
* `.tabs__list-container` - Tab list container wrapper
* `.tabs__list-container__scroller` - Scrollable wrapper around the tab list (handles overflow and fading edges)
* `.tabs__list-container__scroll-prev` - Scroll chevron button for the previous (left / up) direction
* `.tabs__list-container__scroll-next` - Scroll chevron button for the next (right / down) direction
* `.tabs__list` - Tab list container
* `.tabs__tab` - Individual tab button
* `.tabs__separator` - Separator between tabs
* `.tabs__panel` - Tab panel content
* `.tabs__indicator` - Tab indicator
#### Orientation Attributes \[!toc]
* `.tabs[data-orientation="horizontal"]` - Horizontal tab layout (default)
* `.tabs[data-orientation="vertical"]` - Vertical tab layout
#### Variant Classes \[!toc]
* `.tabs--secondary` - Secondary variant with underline indicator
#### Alignment Classes \[!toc]
* `.tabs--align-start` - Aligns tab content to the start
* `.tabs--align-end` - Aligns tab content to the end
### Interactive States
The component supports both CSS pseudo-classes and data attributes:
* **Selected**: `[aria-selected="true"]`
* **Hover**: `:hover` or `[data-hovered="true"]`
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]`
* **Disabled**: `[aria-disabled="true"]`
## API Reference
### Tabs
| Prop | Type | Default | Description |
| -------------------- | ----------------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------- |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual style variant. Primary uses a filled indicator, secondary uses an underline indicator |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Tab layout orientation |
| `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment of the content inside each tab |
| `selectedKey` | `string` | - | Controlled selected tab key |
| `defaultSelectedKey` | `string` | - | Default selected tab key |
| `onSelectionChange` | `(key: Key) => void` | - | Selection change handler |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Tabs.List
| Prop | Type | Default | Description |
| ------------ | -------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `aria-label` | `string` | - | Accessibility label for tab list |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Tabs.Tab
| Prop | Type | Default | Description |
| ------------ | ---------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `id` | `string` | - | Unique tab identifier |
| `isDisabled` | `boolean` | `false` | Whether tab is disabled |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Tabs.Separator
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
### Tabs.Panel
| Prop | Type | Default | Description |
| ----------- | --------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `id` | `string` | - | Panel identifier matching tab id |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
## Related Showcases
## Related Components
## Related Components
* **Breadcrumbs**: Display the user's current location within a hierarchy
# AlertDialog
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/alert-dialog
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(overlays)/alert-dialog.mdx
> Modal dialog for critical confirmations requiring user attention and explicit action
## Usage
```tsx
import { AlertDialog } from "@heroui/react";
```
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function Default() {
return (
Delete Project
Delete project permanently?
This will permanently delete My Awesome Project and all of its
data. This action cannot be undone.
Cancel
Delete Project
);
}
```
## Anatomy
```tsx
import {AlertDialog, Button} from "@heroui/react";
export default () => (
Open Alert Dialog
{/* Optional: Close button */}
{/* Optional: Status icon */}
);
```
## Examples
### Statuses
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function Statuses() {
const examples = [
{
actions: {
cancel: "Stay Signed In",
confirm: "Sign Out",
},
body: "You'll need to sign in again to access your account. Any unsaved changes will be lost.",
classNames: "bg-accent-soft text-accent-soft-foreground",
header: "Sign out of your account?",
status: "accent",
trigger: "Sign Out",
},
{
actions: {
cancel: "Not Yet",
confirm: "Mark Complete",
},
body: "This will mark the task as complete and notify all team members. The task will be moved to your completed list.",
classNames: "bg-success-soft text-success-soft-foreground",
header: "Complete this task?",
status: "success",
trigger: "Complete Task",
},
{
actions: {
cancel: "Keep Editing",
confirm: "Discard",
},
body: "You have unsaved changes that will be permanently lost. Are you sure you want to discard them?",
classNames: "bg-warning-soft text-warning-soft-foreground",
header: "Discard unsaved changes?",
status: "warning",
trigger: "Discard Changes",
},
{
actions: {
cancel: "Cancel",
confirm: "Delete Account",
},
body: "This will permanently delete your account and remove all your data from our servers. This action is irreversible.",
classNames: "bg-danger-soft text-danger-soft-foreground",
header: "Delete your account?",
status: "danger",
trigger: "Delete Account",
},
] as const;
return (
{examples.map(({actions, body, classNames, header, status, trigger}) => (
{trigger}
{header}
{body}
{actions.cancel}
{actions.confirm}
))}
);
}
```
### Placements
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function Placements() {
const placements = ["auto", "top", "center", "bottom"] as const;
return (
{placements.map((placement) => (
{placement.charAt(0).toUpperCase() + placement.slice(1)}
{placement === "auto"
? "Auto Placement"
: `${placement.charAt(0).toUpperCase() + placement.slice(1)} Position`}
{placement === "auto"
? "Automatically positions at the bottom on mobile and center on desktop for optimal user experience."
: `This dialog is positioned at the ${placement} of the viewport. Critical confirmations are typically centered for maximum attention.`}
Cancel
Confirm
))}
);
}
```
### Sizes
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function Sizes() {
const sizes = ["xs", "sm", "md", "lg", "cover"] as const;
return (
{sizes.map((size) => (
{size.charAt(0).toUpperCase() + size.slice(1)}
Size: {size.charAt(0).toUpperCase() + size.slice(1)}
{size === "cover" ? (
<>
This alert dialog uses the cover size variant. It spans the
full screen with margins: 16px on mobile and 40px on desktop. Maintains
rounded corners and standard padding. Perfect for critical confirmations
that need maximum width while preserving alert dialog aesthetics.
>
) : (
<>
This alert dialog uses the {size} size variant. On mobile
devices, all sizes adapt to near full-width for optimal viewing. On desktop,
each size provides a different maximum width to suit various content needs.
>
)}
Cancel
Confirm
))}
);
}
```
### Controlled State
```tsx
"use client";
import {AlertDialog, Button, useOverlayState} from "@heroui/react";
import React from "react";
export function Controlled() {
const [isOpen, setIsOpen] = React.useState(false);
const state = useOverlayState();
return (
With React.useState()
Control the alert dialog using React's useState{" "}
hook for simple state management. Perfect for basic use cases.
Status:{" "}
{isOpen ? "open" : "closed"}
setIsOpen(true)}>
Open Dialog
setIsOpen(!isOpen)}>
Toggle
Controlled with useState()
This alert dialog is controlled by React's useState hook. Pass{" "}
isOpen and onOpenChange props to manage the dialog state
externally.
Cancel
Confirm
With useOverlayState()
Use the useOverlayState hook for a cleaner API
with convenient methods like open(), close(), and{" "}
toggle().
Status:{" "}
{state.isOpen ? "open" : "closed"}
Open Dialog
Toggle
Controlled with useOverlayState()
The useOverlayState hook provides dedicated methods for common
operations. No need to manually create callbacks—just use{" "}
state.open(), state.close(), or{" "}
state.toggle().
Cancel
Confirm
);
}
```
### Custom Icon
```tsx
"use client";
import {LockOpen} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function CustomIcon() {
return (
Reset Password
Reset your password?
We'll send a password reset link to your email address. You'll need to create a new
password to regain access to your account.
Cancel
Send Reset Link
);
}
```
### Custom Trigger
```tsx
"use client";
import {TrashBin} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function CustomTrigger() {
return (
Delete Item
Permanently remove this item
Delete this item?
Use AlertDialog.Trigger to create custom trigger elements beyond
standard buttons. This example shows a card-style trigger with icons and descriptive
text.
Cancel
Delete Item
);
}
```
### Backdrop Variants
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
return (
{variants.map((variant) => (
{variant.charAt(0).toUpperCase() + variant.slice(1)}
Backdrop: {variant.charAt(0).toUpperCase() + variant.slice(1)}
{variant === "opaque"
? "An opaque dark backdrop that completely obscures the background, providing maximum focus on the dialog."
: variant === "blur"
? "A blurred backdrop that softly obscures the background while maintaining visual context."
: "A transparent backdrop that keeps the background fully visible, useful for less critical confirmations."}
Cancel
Confirm
))}
);
}
```
### Custom Backdrop
```tsx
"use client";
import {TriangleExclamation} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function CustomBackdrop() {
return (
Delete Account
Permanently delete your account?
This action cannot be undone. All your data, settings, and content will be
permanently removed from our servers. The dramatic red backdrop emphasizes the
severity and irreversibility of this decision.
Keep Account
Delete Forever
);
}
```
### Dismiss Behavior
```tsx
"use client";
import {CircleInfo} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function DismissBehavior() {
return (
isDismissable
Controls whether the alert dialog can be dismissed by clicking the overlay backdrop. Alert
dialogs typically require explicit action, so this defaults to false. Set to{" "}
true for less critical confirmations.
Open Alert Dialog
isDismissable = false
Clicking the backdrop won't close this alert dialog
Try clicking outside this alert dialog on the overlay - it won't close. You must
use the action buttons to dismiss it.
Cancel
Confirm
isKeyboardDismissDisabled
Controls whether the ESC key can dismiss the alert dialog. Alert dialogs typically require
explicit action, so this defaults to true. When set to false,
the ESC key will be enabled.
Open Alert Dialog
isKeyboardDismissDisabled = true
ESC key is disabled
Press ESC - nothing happens. You must use the action buttons to dismiss this
alert dialog.
Cancel
Confirm
);
}
```
### Close Methods
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function CloseMethods() {
return (
Using slot="close"
The simplest way to close a dialog. Add slot="close" to any Button component
within the dialog. When clicked, it will automatically close the dialog.
Open Dialog
Using slot="close"
Click either button below - both have slot="close" and will close
the dialog automatically.
Cancel
Confirm
Using Dialog render props
Access the close method from the Dialog's render props. This gives you full
control over when and how to close the dialog, allowing you to add custom logic before
closing.
Open Dialog
{(renderProps) => (
<>
Using Dialog render props
The buttons below use the close method from render props. You
can add validation or other logic before calling{" "}
renderProps.close().
renderProps.close()}>
Cancel
renderProps.close()}>Confirm
>
)}
);
}
```
### Custom Animations
```tsx
"use client";
import {ArrowUpFromLine, Sparkles} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
import React from "react";
const iconMap: Record> = {
"gravity-ui:arrow-up-from-line": ArrowUpFromLine,
"gravity-ui:sparkles": Sparkles,
};
export function CustomAnimations() {
const animations = [
{
classNames: {
backdrop: [
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:zoom-in-95",
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:zoom-out-95",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
},
description:
"Physics-based elastic scaling. Simulates a high-damping spring system with fast transient response and prolonged settling time. Ideal for Alert Dialogs and Modals.",
icon: "gravity-ui:sparkles",
name: "Kinematic Scale",
},
{
classNames: {
backdrop: [
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:slide-in-from-bottom-4",
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:slide-out-to-bottom-2",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
},
description:
"Simulates movement through a medium with fluid resistance. Eliminates mechanical linearity for a natural, grounded feel. Perfect for Bottom Sheets or Toasts.",
icon: "gravity-ui:arrow-up-from-line",
name: "Fluid Slide",
},
];
return (
{animations.map(({classNames, description, icon, name}) => {
const IconComponent = iconMap[icon];
return (
{name}
{!!IconComponent && }
{name} Animation
{description}
Close
Try Again
);
})}
);
}
```
### Custom Portal
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
import {useCallback, useRef, useState} from "react";
export function CustomPortal() {
const portalRef = useRef(null);
const [portalContainer, setPortalContainer] = useState(null);
const setPortalRef = useCallback((node: HTMLDivElement | null) => {
portalRef.current = node;
setPortalContainer(node);
}, []);
return (
Render alert dialogs inside a custom container instead of document.body
Apply transform: translateZ(0) to the
container to create a new stacking context.
{!!portalContainer && (
Open Alert Dialog
Custom Portal
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Cancel
Confirm
)}
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function CustomStyles() {
return (
Sign out
Sign out of your account?
You will be signed out on this device. Unsaved work in{" "}
Acme Workspace may be lost unless it
was saved to the cloud.
Stay signed in
Sign out
);
}
```
### Global CSS
To customize the AlertDialog 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-dialog__backdrop {
@apply bg-gradient-to-br from-black/60 to-black/80;
}
.alert-dialog__dialog {
@apply rounded-2xl border border-red-500/20 shadow-2xl;
}
.alert-dialog__header {
@apply gap-4;
}
.alert-dialog__icon {
@apply size-16;
}
.alert-dialog__close-trigger {
@apply rounded-full bg-white/10 hover:bg-white/20;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The AlertDialog component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/alert-dialog.css)):
#### Base Classes \[!toc]
* `.alert-dialog__trigger` - Trigger element that opens the alert dialog
* `.alert-dialog__backdrop` - Overlay backdrop behind the dialog
* `.alert-dialog__container` - Positioning wrapper with placement support
* `.alert-dialog__dialog` - Dialog content container
* `.alert-dialog__header` - Header section for icon and title
* `.alert-dialog__heading` - Heading text styles
* `.alert-dialog__body` - Main content area
* `.alert-dialog__footer` - Footer section for actions
* `.alert-dialog__icon` - Icon container with status colors
* `.alert-dialog__close-trigger` - Close button element
#### Backdrop Variants \[!toc]
* `.alert-dialog__backdrop--opaque` - Opaque colored backdrop (default)
* `.alert-dialog__backdrop--blur` - Blurred backdrop with glass effect
* `.alert-dialog__backdrop--transparent` - Transparent backdrop (no overlay)
#### Status Variants (Icon) \[!toc]
* `.alert-dialog__icon--default` - Default gray status
* `.alert-dialog__icon--accent` - Accent blue status
* `.alert-dialog__icon--success` - Success green status
* `.alert-dialog__icon--warning` - Warning orange status
* `.alert-dialog__icon--danger` - Danger red status
### Interactive States
The component supports these interactive states:
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` - Applied to trigger, dialog, and close button
* **Hover**: `:hover` or `[data-hovered="true"]` - Applied to close button on hover
* **Active**: `:active` or `[data-pressed="true"]` - Applied to close button when pressed
* **Entering**: `[data-entering]` - Applied during dialog opening animation
* **Exiting**: `[data-exiting]` - Applied during dialog closing animation
* **Placement**: `[data-placement="*"]` - Applied based on dialog position (auto, top, center, bottom)
## API Reference
### AlertDialog
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------ |
| `children` | `ReactNode` | - | Trigger and container elements |
### AlertDialog.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `children` | `ReactNode` | - | Custom trigger content |
| `className` | `string` | - | CSS classes |
### AlertDialog.Backdrop
| Prop | Type | Default | Description |
| --------------------------- | ------------------------------------- | ---------- | ------------------------- |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | Backdrop overlay style |
| `isDismissable` | `boolean` | `false` | Close on backdrop click |
| `isKeyboardDismissDisabled` | `boolean` | `true` | Disable ESC key to close |
| `isOpen` | `boolean` | - | Controlled open state |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Open state change handler |
| `className` | `string \| (values) => string` | - | Backdrop CSS classes |
| `UNSTABLE_portalContainer` | `HTMLElement` | - | Custom portal container |
### AlertDialog.Container
| Prop | Type | Default | Description |
| ----------- | ----------------------------------------- | -------- | ------------------------- |
| `placement` | `"auto" \| "center" \| "top" \| "bottom"` | `"auto"` | Dialog position on screen |
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "cover"` | `"md"` | Alert Dialog size variant |
| `className` | `string \| (values) => string` | - | Container CSS classes |
### AlertDialog.Dialog
| Prop | Type | Default | Description |
| ------------------ | ------------------------------------- | --------------- | -------------------------- |
| `children` | `ReactNode \| ({close}) => ReactNode` | - | Content or render function |
| `className` | `string` | - | CSS classes |
| `role` | `string` | `"alertdialog"` | ARIA role |
| `aria-label` | `string` | - | Accessibility label |
| `aria-labelledby` | `string` | - | ID of label element |
| `aria-describedby` | `string` | - | ID of description element |
### AlertDialog.Header
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children` | `ReactNode` | - | Header content (typically Icon and Heading) |
| `className` | `string` | - | CSS classes |
### AlertDialog.Heading
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------ |
| `children` | `ReactNode` | - | Heading text |
| `className` | `string` | - | CSS classes |
### AlertDialog.Body
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------ |
| `children` | `ReactNode` | - | Body content |
| `className` | `string` | - | CSS classes |
### AlertDialog.Footer
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ----------------------------------------- |
| `children` | `ReactNode` | - | Footer content (typically action buttons) |
| `className` | `string` | - | CSS classes |
### AlertDialog.Icon
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------- | ---------- | -------------------- |
| `children` | `ReactNode` | - | Custom icon element |
| `status` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"danger"` | Status color variant |
| `className` | `string` | - | CSS classes |
### AlertDialog.CloseTrigger
| Prop | Type | Default | Description |
| ----------- | ------------------------------ | ------- | ------------------- |
| `children` | `ReactNode` | - | Custom close button |
| `className` | `string \| (values) => string` | - | CSS classes |
### useOverlayState Hook
```tsx
import {useOverlayState} from "@heroui/react";
const state = useOverlayState({
defaultOpen: false,
onOpenChange: (isOpen) => console.log(isOpen),
});
state.isOpen; // Current state
state.open(); // Open dialog
state.close(); // Close dialog
state.toggle(); // Toggle state
state.setOpen(); // Set state directly
```
## Accessibility
Implements [WAI-ARIA AlertDialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/):
* **Focus trap**: Focus locked within alert dialog
* **Keyboard**: `ESC` closes (when enabled), `Tab` cycles elements
* **Screen readers**: Proper ARIA attributes with `role="alertdialog"`
* **Scroll lock**: Body scroll disabled when open
* **Required action**: Defaults to requiring explicit user action (no backdrop/ESC dismiss)
## Related Components
## Related Components
* **Button**: Allows a user to perform an action
* **CloseButton**: Button for dismissing overlays
# Drawer
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/drawer
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(overlays)/drawer.mdx
> Slide-out panel for supplementary content and actions
## Usage
```tsx
import { Drawer, Button } from "@heroui/react";
```
```tsx
import {Button, Drawer} from "@heroui/react";
export function Basic() {
return (
Open Drawer
Drawer Title
This is a bottom drawer built with React Aria's Modal component. It slides up from
the bottom of the screen with a smooth CSS transition.
Cancel
Confirm
);
}
```
## Anatomy
```tsx
import { Drawer, Button } from "@heroui/react";
export default () => (
Open Drawer
{/* Optional: Drag handle */}
{/* Optional: Close button */}
);
```
## Examples
### Placement
```tsx
import {Button, Drawer} from "@heroui/react";
export function Placements() {
const placements = ["bottom", "top", "left", "right"] as const;
return (
{placements.map((placement) => (
{placement.charAt(0).toUpperCase() + placement.slice(1)}
{placement === "bottom" && }
{placement.charAt(0).toUpperCase() + placement.slice(1)} Drawer
This drawer slides in from the {placement} edge of the screen.
Cancel
Done
{placement === "top" && }
))}
);
}
```
### Non-Dismissable
Set `isDismissable={false}` on `Drawer.Backdrop` to prevent closing by clicking outside or dragging. The user must interact with the drawer's action buttons.
```tsx
import {Button, Drawer} from "@heroui/react";
export function NonDismissable() {
return (
Important Action
Confirm Action
This drawer cannot be dismissed by clicking outside or dragging. You must use one of
the buttons below.
Cancel
Confirm
);
}
```
### Scrollable Content
The `Drawer.Body` automatically handles overflow with native scrolling. Drag-to-dismiss is excluded from the body area to avoid scroll conflicts.
```tsx
import {Button, Drawer} from "@heroui/react";
export function ScrollableContent() {
return (
Terms & Conditions
Terms & Conditions
{Array.from({length: 20}).map((_, i) => (
Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit
risus, sed porttitor quam.
))}
Decline
Accept
);
}
```
### Controlled State
```tsx
"use client";
import {Button, Drawer, useOverlayState} from "@heroui/react";
import React from "react";
export function Controlled() {
const [isOpen, setIsOpen] = React.useState(false);
const state = useOverlayState();
return (
With React.useState()
Control the drawer using React's useState hook
for simple state management.
Status:{" "}
{isOpen ? "open" : "closed"}
setIsOpen(true)}>
Open Drawer
setIsOpen(!isOpen)}>
Toggle
Controlled with useState()
This drawer is controlled by React's useState hook. Pass{" "}
isOpen and onOpenChange props to manage the drawer state
externally.
Close
With useOverlayState()
Use the useOverlayState hook for a cleaner API
with convenient methods like open(), close(), and{" "}
toggle().
Status:{" "}
{state.isOpen ? "open" : "closed"}
Open Drawer
Toggle
Controlled with useOverlayState()
The useOverlayState hook provides dedicated methods for common
operations. No need to manually create callbacks—just use{" "}
state.open(), state.close(), or{" "}
state.toggle().
Close
);
}
```
### With Form
```tsx
import {Button, Drawer, Input, Label, TextField} from "@heroui/react";
export function WithForm() {
return (
Edit Profile
Edit Profile
Name
Email
Bio
Cancel
Save Changes
);
}
```
### Navigation Drawer
```tsx
import type {ComponentType, SVGProps} from "react";
import {Bars, Bell, Envelope, Gear, House, Magnifier, Person} from "@gravity-ui/icons";
import {Button, Drawer} from "@heroui/react";
export function Navigation() {
const navItems: {icon: ComponentType>; label: string}[] = [
{icon: House, label: "Home"},
{icon: Magnifier, label: "Search"},
{icon: Bell, label: "Notifications"},
{icon: Envelope, label: "Messages"},
{icon: Person, label: "Profile"},
{icon: Gear, label: "Settings"},
];
return (
Menu
Navigation
{navItems.map((item) => (
{item.label}
))}
);
}
```
### Backdrop Variants
```tsx
import {Button, Drawer} from "@heroui/react";
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
return (
{variants.map((variant) => (
{variant.charAt(0).toUpperCase() + variant.slice(1)}
Backdrop: {variant.charAt(0).toUpperCase() + variant.slice(1)}
This drawer uses the {variant} backdrop variant.
Close
))}
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Button, Drawer} from "@heroui/react";
export function CustomStyles() {
return (
Open filters
Filters
Narrow results by status, owner, or date.
Cancel
Apply
);
}
```
### Global CSS
To customize the Drawer component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.drawer__backdrop {
@apply bg-gradient-to-br from-black/50 to-black/70;
}
.drawer__dialog {
@apply rounded-2xl border border-white/10 shadow-2xl;
}
.drawer__header {
@apply text-center;
}
.drawer__close-trigger {
@apply rounded-full bg-white/10 hover:bg-white/20;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Drawer component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/drawer.css)):
#### Base Classes \[!toc]
* `.drawer__trigger` - Trigger element that opens the drawer
* `.drawer__backdrop` - Overlay backdrop behind the drawer
* `.drawer__content` - Positioning wrapper for the drawer panel
* `.drawer__dialog` - The drawer panel itself
* `.drawer__header` - Header section for titles
* `.drawer__heading` - Main title text
* `.drawer__body` - Main scrollable content area
* `.drawer__footer` - Footer section for actions
* `.drawer__handle` - Visual drag handle indicator
* `.drawer__close-trigger` - Close button element
#### Backdrop Variants \[!toc]
* `.drawer__backdrop--opaque` - Opaque colored backdrop (default)
* `.drawer__backdrop--blur` - Blurred backdrop with glass effect
* `.drawer__backdrop--transparent` - Transparent backdrop (no overlay)
#### Placement Variants \[!toc]
* `.drawer__content--bottom` - Slides up from the bottom edge (default)
* `.drawer__content--top` - Slides down from the top edge
* `.drawer__content--left` - Slides in from the left edge
* `.drawer__content--right` - Slides in from the right edge
#### Dialog Variants \[!toc]
* `.drawer__dialog--top` - Slides down from the top edge
* `.drawer__dialog--bottom` - Slides up from the bottom edge
* `.drawer__dialog--left` - Slides in from the left edge
* `.drawer__dialog--right` - Slides in from the right edge
### Interactive States
The component supports these interactive states:
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` - Applied to trigger and close button
* **Hover**: `:hover` or `[data-hovered="true"]` - Applied to close button on hover
* **Active**: `:active` or `[data-pressed="true"]` - Applied to trigger and close button when pressed
* **Entering**: `[data-entering]` - Applied during drawer opening animation
* **Exiting**: `[data-exiting]` - Applied during drawer closing animation
* **Placement**: `[data-placement="*"]` - Applied based on drawer position (top, bottom, left, right)
## API Reference
### Drawer
| Prop | Type | Default | Description |
| ---------- | ----------------------- | ------- | ----------------------------- |
| `children` | `ReactNode` | - | Trigger and backdrop elements |
| `state` | `UseOverlayStateReturn` | - | Controlled overlay state |
### Drawer.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `children` | `ReactNode` | - | Custom trigger content |
| `className` | `string` | - | CSS classes |
### Drawer.Backdrop
| Prop | Type | Default | Description |
| --------------------------- | ------------------------------------- | ---------- | ------------------------- |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | Backdrop overlay style |
| `isDismissable` | `boolean` | `true` | Close on backdrop click |
| `isKeyboardDismissDisabled` | `boolean` | `false` | Disable ESC key to close |
| `isOpen` | `boolean` | - | Controlled open state |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Open state change handler |
| `className` | `string \| (values) => string` | - | Backdrop CSS classes |
### Drawer.Content
| Prop | Type | Default | Description |
| ----------- | ---------------------------------------- | ---------- | --------------------------- |
| `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Edge the drawer slides from |
| `className` | `string \| (values) => string` | - | Content CSS classes |
### Drawer.Dialog
| Prop | Type | Default | Description |
| ----------------- | ----------- | ---------- | ------------------- |
| `children` | `ReactNode` | - | Dialog content |
| `className` | `string` | - | CSS classes |
| `role` | `string` | `"dialog"` | ARIA role |
| `aria-label` | `string` | - | Accessibility label |
| `aria-labelledby` | `string` | - | ID of label element |
### Drawer.Header
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------- |
| `children` | `ReactNode` | - | Header content |
| `className` | `string` | - | CSS classes |
### Drawer.Heading
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ----------- |
| `children` | `ReactNode` | - | Title text |
| `className` | `string` | - | CSS classes |
### Drawer.Body
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------ |
| `children` | `ReactNode` | - | Body content |
| `className` | `string` | - | CSS classes |
### Drawer.Footer
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------- |
| `children` | `ReactNode` | - | Footer content |
| `className` | `string` | - | CSS classes |
### Drawer.Handle
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ----------- |
| `className` | `string` | - | CSS classes |
### Drawer.CloseTrigger
| Prop | Type | Default | Description |
| ----------- | ------------------------------ | ------- | ------------------- |
| `children` | `ReactNode` | - | Custom close button |
| `className` | `string \| (values) => string` | - | CSS classes |
### useOverlayState Hook
```tsx
import { useOverlayState } from "@heroui/react";
const state = useOverlayState({
defaultOpen: false,
onOpenChange: (isOpen) => console.log(isOpen),
});
state.isOpen; // Current state
state.open(); // Open drawer
state.close(); // Close drawer
state.toggle(); // Toggle state
state.setOpen(); // Set state directly
```
## Accessibility
Implements [WAI-ARIA Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/):
* **Focus trap**: Focus locked within drawer when open
* **Keyboard**: `ESC` closes (when dismissable), `Tab` cycles elements
* **Screen readers**: Proper ARIA attributes via React Aria
* **Scroll lock**: Body scroll disabled when open
* **Drag to dismiss**: Supports pointer-based drag gestures on handle, header, and footer areas
## Related Components
## Related Components
* **Modal**: Displays content in a modal overlay
* **Button**: Allows a user to perform an action
* **CloseButton**: Button for dismissing overlays
# Modal
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/modal
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(overlays)/modal.mdx
> Dialog overlay for focused user interactions and important content
## Usage
```tsx
import { Modal } from "@heroui/react";
```
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function Default() {
return (
Open Modal
Welcome to HeroUI
A beautiful, fast, and modern React UI library for building accessible and
customizable web applications with ease.
Continue
);
}
```
## Anatomy
```tsx
import {Modal, Button} from "@heroui/react";
export default () => (
Open Modal
{/* Optional: Close button */}
{/* Optional: Icon */}
);
```
## Examples
### Sizes
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function Sizes() {
const sizes = ["xs", "sm", "md", "lg", "cover", "full"] as const;
return (
{sizes.map((size) => (
{size.charAt(0).toUpperCase() + size.slice(1)}
Size: {size.charAt(0).toUpperCase() + size.slice(1)}
{size === "cover" ? (
<>
This modal uses the cover size variant. It spans the full
screen with margins: 16px on mobile and 40px on desktop. Maintains rounded
corners and standard padding. Perfect for cover-style content that needs
maximum width while preserving modal aesthetics.
>
) : size === "full" ? (
<>
This modal uses the full size variant. It occupies the entire
viewport without any margins, rounded corners, or shadows, creating a true
fullscreen experience. Ideal for immersive content or full-page
interactions.
>
) : (
<>
This modal uses the {size} size variant. On mobile devices, all
sizes adapt to near full-width for optimal viewing. On desktop, each size
provides a different maximum width to suit various content needs.
>
)}
Cancel
Confirm
))}
);
}
```
### Placement
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function Placements() {
const placements = ["auto", "top", "center", "bottom"] as const;
return (
{placements.map((placement) => (
{placement.charAt(0).toUpperCase() + placement.slice(1)}
Placement: {placement.charAt(0).toUpperCase() + placement.slice(1)}
This modal uses the {placement} placement option. Try different
placements to see how the modal positions itself on the screen.
Continue
))}
);
}
```
### Scroll Behavior
```tsx
"use client";
import {Button, Modal, Radio, RadioGroup} from "@heroui/react";
import {useState} from "react";
export function ScrollComparison() {
const [scroll, setScroll] = useState<"inside" | "outside">("inside");
return (
setScroll(value as "inside" | "outside")}
>
Inside
Outside
Open Modal ({scroll.charAt(0).toUpperCase() + scroll.slice(1)})
Scroll: {scroll.charAt(0).toUpperCase() + scroll.slice(1)}
Compare scroll behaviors - inside keeps content scrollable within the modal,
outside allows page scrolling
{Array.from({length: 30}).map((_, i) => (
Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nullam pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet
hendrerit risus, sed porttitor quam.
))}
Cancel
Confirm
);
}
```
### Controlled State
```tsx
"use client";
import {CircleCheck} from "@gravity-ui/icons";
import {Button, Modal, useOverlayState} from "@heroui/react";
import React from "react";
export function Controlled() {
const [isOpen, setIsOpen] = React.useState(false);
const state = useOverlayState();
return (
With React.useState()
Control the modal using React's useState hook for
simple state management. Perfect for basic use cases.
Status:{" "}
{isOpen ? "open" : "closed"}
setIsOpen(true)}>
Open Modal
setIsOpen(!isOpen)}>
Toggle
Controlled with useState()
This modal is controlled by React's useState hook. Pass{" "}
isOpen and onOpenChange props to manage the modal state
externally.
Cancel
Confirm
With useOverlayState()
Use the useOverlayState hook for a cleaner API
with convenient methods like open(), close(), and{" "}
toggle().
Status:{" "}
{state.isOpen ? "open" : "closed"}
Open Modal
Toggle
Controlled with useOverlayState()
The useOverlayState hook provides dedicated methods for common
operations. No need to manually create callbacks—just use{" "}
state.open(), state.close(), or{" "}
state.toggle().
Cancel
Confirm
);
}
```
### With Form
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Button, Input, Label, Modal, Surface, TextField} from "@heroui/react";
export function WithForm() {
return (
Open Contact Form
Contact Us
Fill out the form below and we'll get back to you. The modal adapts automatically
when the keyboard appears on mobile.
Name
Email
Phone
Company
Message
Cancel
Send Message
);
}
```
### Custom Trigger
```tsx
"use client";
import {Gear} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CustomTrigger() {
return (
Settings
Manage your preferences
Settings
Use Modal.Trigger to create custom trigger elements beyond standard
buttons. This example shows a card-style trigger with icons and descriptive text.
Cancel
Save
);
}
```
### Backdrop Variants
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
return (
{variants.map((variant) => (
{variant.charAt(0).toUpperCase() + variant.slice(1)}
Backdrop: {variant.charAt(0).toUpperCase() + variant.slice(1)}
This modal uses the {variant} backdrop variant. Compare the
different visual effects: opaque provides full opacity, blur adds a backdrop
filter, and transparent removes the background.
Continue
))}
);
}
```
### Custom Backdrop
```tsx
"use client";
import {Sparkles} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CustomBackdrop() {
return (
Custom Backdrop
Premium Backdrop
This backdrop features a sophisticated gradient that transitions from a dark color
at the bottom to complete transparency at the top, combined with a smooth blur
effect. The gradient automatically adapts its intensity for optimal contrast in both
light and dark modes.
Amazing!
Close
);
}
```
### Dismiss Behavior
```tsx
"use client";
import {CircleInfo} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function DismissBehavior() {
return (
isDismissable
Controls whether the modal can be dismissed by clicking the overlay backdrop. Defaults to{" "}
true. Set to false to require explicit close action.
Open Modal
isDismissable = false
Clicking the backdrop won't close this modal
Try clicking outside this modal on the overlay - it won't close. You must use
the close button or press ESC to dismiss it.
Close
isKeyboardDismissDisabled
Controls whether the ESC key can dismiss the modal. When set to true, the ESC
key will be disabled and users must use explicit close actions.
Open Modal
isKeyboardDismissDisabled = true
ESC key is disabled
Press ESC - nothing happens. You must use the close button or click the overlay
backdrop to dismiss this modal.
Close
);
}
```
### Close Methods
```tsx
"use client";
import {CircleCheck, CircleInfo} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CloseMethods() {
return (
Using slot="close"
The simplest way to close a modal. Add slot="close" to any Button component
within the modal. When clicked, it will automatically close the modal.
Open Modal
Using slot="close"
Click either button below - both have slot="close" and will close
the modal automatically.
Cancel
Confirm
Using Dialog render props
Access the close method from the Dialog's render props. This gives you full
control over when and how to close the modal, allowing you to add custom logic before
closing.
Open Modal
{(renderProps) => (
<>
Using Dialog render props
The buttons below use the close method from render props. You
can add validation or other logic before calling{" "}
renderProps.close().
renderProps.close()}>
Cancel
renderProps.close()}>Confirm
>
)}
);
}
```
### Custom Animations
```tsx
"use client";
import {ArrowUpFromLine, Sparkles} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
import React from "react";
const iconMap: Record> = {
"gravity-ui:arrow-up-from-line": ArrowUpFromLine,
"gravity-ui:sparkles": Sparkles,
};
export function CustomAnimations() {
const animations = [
{
classNames: {
backdrop: [
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:zoom-in-95",
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:zoom-out-95",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
},
description:
"Physics-based elastic scaling. Simulates a high-damping spring system with fast transient response and prolonged settling time. Ideal for Modals and Popovers.",
icon: "gravity-ui:sparkles",
name: "Kinematic Scale",
},
{
classNames: {
backdrop: [
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:slide-in-from-bottom-4",
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:slide-out-to-bottom-2",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
},
description:
"Simulates movement through a medium with fluid resistance. Eliminates mechanical linearity for a natural, grounded feel. Perfect for Bottom Sheets or Toasts.",
icon: "gravity-ui:arrow-up-from-line",
name: "Fluid Slide",
},
];
return (
{animations.map(({classNames, description, icon, name}) => {
const IconComponent = iconMap[icon];
return (
{name}
{!!IconComponent && }
{name} Animation
{description}
Close
Try Again
);
})}
);
}
```
### Custom Portal
```tsx
"use client";
import {Button, Modal} from "@heroui/react";
import {useCallback, useRef, useState} from "react";
export function CustomPortal() {
const portalRef = useRef(null);
const [portalContainer, setPortalContainer] = useState(null);
const setPortalRef = useCallback((node: HTMLDivElement | null) => {
portalRef.current = node;
setPortalContainer(node);
}, []);
return (
Render modals inside a custom container instead of document.body
Apply transform: translateZ(0) to the
container to create a new stacking context.
{!!portalContainer && (
Open Modal
Custom Portal
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Close
)}
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import {CircleCheck} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CustomStyles() {
return (
Open
Changes saved
Your draft is synced across devices.
Done
);
}
```
### Global CSS
To customize the Modal component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.modal__backdrop {
@apply bg-gradient-to-br from-black/50 to-black/70;
}
.modal__dialog {
@apply rounded-2xl border border-white/10 shadow-2xl;
}
.modal__header {
@apply text-center;
}
.modal__close-trigger {
@apply rounded-full bg-white/10 hover:bg-white/20;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Modal component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/modal.css)):
#### Base Classes \[!toc]
* `.modal__trigger` - Trigger element that opens the modal
* `.modal__backdrop` - Overlay backdrop behind the modal
* `.modal__container` - Positioning wrapper with placement support
* `.modal__dialog` - Modal content container
* `.modal__header` - Header section for titles and icons
* `.modal__body` - Main content area
* `.modal__footer` - Footer section for actions
* `.modal__close-trigger` - Close button element
#### Backdrop Variants \[!toc]
* `.modal__backdrop--opaque` - Opaque colored backdrop (default)
* `.modal__backdrop--blur` - Blurred backdrop with glass effect
* `.modal__backdrop--transparent` - Transparent backdrop (no overlay)
#### Scroll Variants \[!toc]
* `.modal__container--scroll-outside` - Enables scrolling the entire modal
* `.modal__dialog--scroll-inside` - Constrains modal height for body scrolling
* `.modal__body--scroll-inside` - Makes only the body scrollable
* `.modal__body--scroll-outside` - Allows full-page scrolling
### Interactive States
The component supports these interactive states:
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]` - Applied to trigger, dialog, and close button
* **Hover**: `:hover` or `[data-hovered="true"]` - Applied to close button on hover
* **Active**: `:active` or `[data-pressed="true"]` - Applied to close button when pressed
* **Entering**: `[data-entering]` - Applied during modal opening animation
* **Exiting**: `[data-exiting]` - Applied during modal closing animation
* **Placement**: `[data-placement="*"]` - Applied based on modal position (auto, top, center, bottom)
## API Reference
### Modal
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------ |
| `children` | `ReactNode` | - | Trigger and container elements |
### Modal.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `children` | `ReactNode` | - | Custom trigger content |
| `className` | `string` | - | CSS classes |
### Modal.Backdrop
| Prop | Type | Default | Description |
| --------------------------- | ------------------------------------- | ---------- | ------------------------- |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | Backdrop overlay style |
| `isDismissable` | `boolean` | `true` | Close on backdrop click |
| `isKeyboardDismissDisabled` | `boolean` | `false` | Disable ESC key to close |
| `isOpen` | `boolean` | - | Controlled open state |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Open state change handler |
| `className` | `string \| (values) => string` | - | Backdrop CSS classes |
| `UNSTABLE_portalContainer` | `HTMLElement` | - | Custom portal container |
### Modal.Container
| Prop | Type | Default | Description |
| ----------- | --------------------------------------------------- | ---------- | ------------------------ |
| `placement` | `"auto" \| "center" \| "top" \| "bottom"` | `"auto"` | Modal position on screen |
| `scroll` | `"inside" \| "outside"` | `"inside"` | Scroll behavior |
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "cover" \| "full"` | `"md"` | Modal size variant |
| `className` | `string \| (values) => string` | - | Container CSS classes |
### Modal.Dialog
| Prop | Type | Default | Description |
| ------------------ | ------------------------------------- | ---------- | -------------------------- |
| `children` | `ReactNode \| ({close}) => ReactNode` | - | Content or render function |
| `className` | `string \| (values) => string` | - | CSS classes |
| `role` | `string` | `"dialog"` | ARIA role |
| `aria-label` | `string` | - | Accessibility label |
| `aria-labelledby` | `string` | - | ID of label element |
| `aria-describedby` | `string` | - | ID of description element |
### Modal.Header
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------- |
| `children` | `ReactNode` | - | Header content |
| `className` | `string` | - | CSS classes |
### Modal.Body
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------ |
| `children` | `ReactNode` | - | Body content |
| `className` | `string` | - | CSS classes |
### Modal.Footer
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------- |
| `children` | `ReactNode` | - | Footer content |
| `className` | `string` | - | CSS classes |
### Modal.CloseTrigger
| Prop | Type | Default | Description |
| ----------- | ------------------------------ | ------- | ------------------- |
| `children` | `ReactNode` | - | Custom close button |
| `className` | `string \| (values) => string` | - | CSS classes |
### useOverlayState Hook
```tsx
import {useOverlayState} from "@heroui/react";
const state = useOverlayState({
defaultOpen: false,
onOpenChange: (isOpen) => console.log(isOpen),
});
state.isOpen; // Current state
state.open(); // Open modal
state.close(); // Close modal
state.toggle(); // Toggle state
state.setOpen(); // Set state directly
```
## Accessibility
Implements [WAI-ARIA Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/):
* **Focus trap**: Focus locked within modal
* **Keyboard**: `ESC` closes (when enabled), `Tab` cycles elements
* **Screen readers**: Proper ARIA attributes
* **Scroll lock**: Body scroll disabled when open
## Related Components
## Related Components
* **Button**: Allows a user to perform an action
* **Tooltip**: Contextual information on hover or focus
* **Select**: Dropdown select control
# Popover
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/popover
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(overlays)/popover.mdx
> Displays rich content in a portal triggered by a button or any custom element
## Usage
```tsx
import { Popover } from '@heroui/react';
```
```tsx
import {Button, Popover} from "@heroui/react";
export function PopoverBasic() {
return (
Click me
Popover Title
This is the popover content. You can put any content here.
);
}
```
## Anatomy
```tsx
import { Popover } from '@heroui/react';
export default () => (
{/* content goes here */}
)
```
## Examples
### With Arrow
```tsx
import {Ellipsis} from "@gravity-ui/icons";
import {Button, Popover} from "@heroui/react";
export function PopoverWithArrow() {
return (
With Arrow
Popover with Arrow
The arrow shows which element triggered the popover.
Popover with Arrow
The arrow shows which element triggered the popover.
);
}
```
### Interactive Content
```tsx
"use client";
import {Avatar, Button, Popover} from "@heroui/react";
import {useState} from "react";
export function PopoverInteractive() {
const [isFollowing, setIsFollowing] = useState(false);
return (
setIsFollowing(!isFollowing)}
>
{isFollowing ? "Following" : "Follow"}
Product designer and creative director. Building beautiful experiences that matter.
892
Following
12.5K
Followers
);
}
```
### Placement
```tsx
import {Button, Popover} from "@heroui/react";
export function PopoverPlacement() {
return (
Top
Top placement
Left
Left placement
Click buttons
Right
Right placement
Bottom
Bottom placement
);
}
```
### Render Function
```tsx
"use client";
import {Button, Popover} from "@heroui/react";
export function RenderFunction() {
return (
Click me
}
>
Popover Title
This is the popover content. You can put any content here.
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Button, Popover} from "@heroui/react";
export function CustomStyles() {
return (
Details
Keyboard shortcuts
Save
⌘ S
Search
⌘ K
);
}
```
### Global CSS
To customize the Popover component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.popover {
@apply rounded-xl shadow-2xl;
}
.popover__dialog {
@apply p-4;
}
.popover__heading {
@apply text-lg font-bold;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Popover component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/popover.css)):
#### Base Classes \[!toc]
* `.popover` - Base popover container styles
* `.popover__dialog` - Dialog content wrapper
* `.popover__heading` - Heading text styles
* `.popover__trigger` - Trigger element styles
### Interactive States
The component supports animation states:
* **Entering**: `[data-entering]` - Applied during popover appearance
* **Exiting**: `[data-exiting]` - Applied during popover disappearance
* **Placement**: `[data-placement="*"]` - Applied based on popover position
* **Focus**: `:focus-visible` or `[data-focus-visible="true"]`
## API Reference
### Popover
| Prop | Type | Default | Description |
| -------------- | --------------------------- | ------- | ---------------------------------------- |
| `children` | `React.ReactNode` | - | Trigger and content elements |
| `isOpen` | `boolean` | - | Controls popover visibility (controlled) |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Called when open state changes |
### Popover.Content
| Prop | Type | Default | Description |
| ------------ | -------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | Content to display in the popover |
| `placement` | `"top" \| "bottom" \| "left" \| "right"` (and variants) | `"bottom"` | Placement of the popover |
| `offset` | `number` | `8` | Distance from the trigger element |
| `shouldFlip` | `boolean` | `true` | Whether popover can change orientation to fit |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Popover.Dialog
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `children` | `React.ReactNode` | - | Dialog content |
| `className` | `string` | - | Additional CSS classes |
### Popover.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | --------------------------------- |
| `children` | `React.ReactNode` | - | Element that triggers the popover |
| `className` | `string` | - | Additional CSS classes |
### Popover.Arrow
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | Custom arrow element |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
## Related Components
## Related Components
* **Button**: Allows a user to perform an action
* **Tooltip**: Contextual information on hover or focus
* **Select**: Dropdown select control
# Sheet
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/sheet
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(overlays)/sheet.mdx
> Swipeable overlay with native scroll snapping, partial positions, and nested sheets
## Usage
```tsx
import {Button, Sheet} from "@heroui/react";
```
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function Basic() {
return (
Open Sheet
Sheet Title
This is a bottom sheet built with smooth drag-to-dismiss animations. Try dragging
the handle down to close, or use the close button.
Cancel
Confirm
);
}
```
## Anatomy
```tsx
import {Button, Sheet} from "@heroui/react";
export default () => (
Open sheet
Details
Sheet content
Done
);
```
`Sheet.Root` is an alias for `Sheet`. `Sheet.Trigger` wraps a pressable child such as `Button` and is placed directly inside the root. Provide a `Sheet.Heading`, `aria-label`, or `aria-labelledby` for the dialog's accessible name. Use `Sheet.Close`, `Sheet.CloseTrigger`, or a button with `slot="close"` for keyboard-accessible dismissal; the handle is decorative to assistive technology.
## Examples
### Placements
Set `placement` on the root to `bottom` (default), `top`, `left`, or `right`.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function Placements() {
const placements = ["bottom", "top", "left", "right"] as const;
return (
{placements.map((p) => (
{p.charAt(0).toUpperCase() + p.slice(1)}
{p === "bottom" && }
{p.charAt(0).toUpperCase() + p.slice(1)} Sheet
This sheet slides in from the {p} edge of the screen with a
smooth spring-like animation.
Cancel
Done
{p === "top" && }
))}
);
}
```
### Snap Points
Numbers are **pixels**, not fractions. Strings are CSS lengths; percentages are relative to the sheet. The sheet opens at the first point and can expand to full size without an explicit final point. A trailing `1`, `"1"`, or `"100%"` is ignored. Other numbers retain their pixel meaning.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const snapPointValues = ["148px", "355px"];
export function SnapPoints() {
return (
Snap Points
Snap Points
This sheet opens at 148px and snaps again at 355px. Dragging past the last detent
reveals the full sheet, and the backdrop fades in after that point.
{Array.from({length: 6}).map((_, i) => (
{i === 0
? "Swipe up to reveal more content and fade the backdrop in."
: `More content at this level (${i + 1}).`}
))}
);
}
```
### Snap Points (Sequential)
A swipe can pass through multiple snap points. Full expansion is implicit; no additional sequential-snapping prop is needed.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const snapPointValues = ["148px", "355px"];
export function SnapPointsSequential() {
return (
Sequential Snap Points
Snap points
A swipe can move across several detents. Full height is the end of the gesture, so
it is not listed as its own snap point.
);
}
```
### Snap Points (Custom Fade)
The backdrop remains clear through the partial snap points and fades in as the sheet expands past the last point. React Aria manages this behavior; `fadeFromIndex` is not supported.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const fadeSnapPoints = ["150px", "300px", "450px"];
export function SnapPointsCustomFade() {
return (
Custom Fade Index
Backdrop fade
The backdrop stays clear through the snap points and fades in only after the last
detent, as the sheet opens to its full height.
);
}
```
### Backdrop Variants
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
return (
{variants.map((v) => (
{v.charAt(0).toUpperCase() + v.slice(1)}
Backdrop: {v.charAt(0).toUpperCase() + v.slice(1)}
This sheet uses the {v} backdrop
variant.
Close
))}
);
}
```
### Non-Dismissable
`isDismissable={false}` prevents outside interaction, Escape, and swipe dismissal. Explicit close controls still work.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function NonDismissable() {
return (
Important Action
Confirm Action
This sheet cannot be dismissed by clicking outside or dragging. You must use one of
the buttons below.
Cancel
Confirm
);
}
```
### Scrollable Content
At a partial snap point, swiping expands the sheet. Once fully expanded, `Sheet.Body` scrolls its content. `Sheet.Handle` also supports pointer dragging, including a mouse.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function ScrollableContent() {
return (
Terms & Conditions
Terms & Conditions
{Array.from({length: 20}).map((_, i) => (
Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit
risus, sed porttitor quam. Donec nec vestibulum libero.
))}
Decline
Accept
);
}
```
### With Form
Compose form fields inside `Sheet.Body`. Use secondary inputs on the overlay surface.
```tsx
"use client";
import {Button, Input, Label, Sheet, TextField} from "@heroui/react";
export function WithForm() {
return (
Edit Profile
Edit Profile
Name
Email
Bio
Cancel
Save Changes
);
}
```
### Detached
`isDetached` adds space around the sheet and rounds all corners.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function Detached() {
const placements = ["bottom", "top", "left", "right"] as const;
return (
{placements.map((p) => (
{p.charAt(0).toUpperCase() + p.slice(1)}
{p === "bottom" && }
Detached {p}
The sheet floats away from the viewport edge with rounded corners on all sides.
{p === "top" && }
))}
);
}
```
### Controlled State
Use `isOpen` with `onOpenChange`, or `defaultOpen` for uncontrolled state. A controlled sheet may omit `Sheet.Trigger`.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [isOpen, setIsOpen] = useState(false);
return (
setIsOpen(true)}>
Open Sheet
setIsOpen(!isOpen)}>
Toggle
Status:{" "}
{isOpen ? "open" : "closed"}
Controlled Sheet
This sheet is controlled via isOpen and onOpenChange{" "}
props. The parent manages the state externally.
Close
);
}
```
### Handle Only
The handle offers a visible grip and supports pointer dragging. Touch gestures also work on the panel until it is fully expanded; the handle does not restrict swipes to itself.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function HandleOnly() {
return (
Handle Only
Swipe the sheet
The handle is a visual grip. Swipe anywhere on the sheet until it is fully expanded.
After that, the body scrolls instead of moving the sheet.
Done
);
}
```
### Nested Sheets
Place `Sheet.NestedRoot` inside another sheet. React Aria manages stacking and focus restoration; only the bottom sheet dims the page.
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const NESTED_DIALOG_HEIGHT = "h-[320px]";
export function Nested() {
return (
Open Parent Sheet
Parent Sheet
This is the parent sheet. Open a nested sheet from here — the parent will scale down
and the child slides on top.
Open Nested Sheet
Nested Sheet
This is a nested sheet that sits on top of the parent. Drag it down to
dismiss and return to the parent sheet.
Go Deeper
Third Level
Three levels deep! Each parent sheet scales down as the next one
opens, creating a stacking effect.
Close
Back
);
}
```
### Professions Picker
Combine a search field and a virtualized list inside a sheet with a 355px initial snap point. Selecting an occupation updates the value and closes the sheet.
```tsx
"use client";
import {
Button,
EmptyState,
Label,
ListBox,
ListLayout,
SearchField,
Sheet,
Virtualizer,
} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useMemo, useState} from "react";
const PROFESSIONS = [
"Accountants and Auditors",
"Actors",
"Actuaries",
"Administrative Services Managers",
"Advertising and Promotions Managers",
"Advertising Sales Agents",
"Aerospace Engineers",
"Agricultural Engineers",
"Air Traffic Controllers",
"Aircraft Mechanics and Service Technicians",
"Ambulance Drivers and Attendants",
"Anesthesiologists",
"Animal Control Workers",
"Animators",
"Arbitrators, Mediators, and Conciliators",
"Architects, Except Landscape and Naval",
"Architectural and Engineering Managers",
"Archivists",
"Art Directors",
"Athletic Trainers",
"Atmospheric and Space Scientists",
"Audiologists",
"Automotive Body and Related Repairers",
"Automotive Service Technicians and Mechanics",
"Baggage Porters and Bellhops",
"Bakers",
"Barbers",
"Bartenders",
"Bicycle Repairers",
"Billing and Posting Clerks",
"Biochemists and Biophysicists",
"Biomedical Engineers",
"Bookkeeping, Accounting, and Auditing Clerks",
"Brickmasons and Blockmasons",
"Broadcast Technicians",
"Budget Analysts",
"Bus Drivers",
"Business Operations Specialists",
"Butchers and Meat Cutters",
"Cabinetmakers and Bench Carpenters",
"Camera Operators, Television, Video, and Film",
"Cardiovascular Technologists and Technicians",
"Cargo and Freight Agents",
"Carpenters",
"Cashiers",
"Cement Masons and Concrete Finishers",
"Chefs and Head Cooks",
"Chemical Engineers",
"Chemists",
"Chief Executives",
"Child, Family, and School Social Workers",
"Chiropractors",
"Civil Engineers",
"Claims Adjusters, Examiners, and Investigators",
"Clinical Laboratory Technologists and Technicians",
"Coaches and Scouts",
"Commercial Pilots",
"Community Health Workers",
"Compensation and Benefits Managers",
"Computer and Information Systems Managers",
"Computer Hardware Engineers",
"Computer Network Architects",
"Computer Programmers",
"Computer Systems Analysts",
"Conservation Scientists",
"Construction Laborers",
"Construction Managers",
"Correctional Officers and Jailers",
"Cost Estimators",
"Counselors",
"Court Reporters and Simultaneous Captioners",
"Credit Analysts",
"Curators",
"Customer Service Representatives",
"Cybersecurity Analysts",
"Data Scientists",
"Database Administrators",
"Dental Assistants",
"Dental Hygienists",
"Dentists",
"Detectives and Criminal Investigators",
"Diagnostic Medical Sonographers",
"Dietitians and Nutritionists",
"Directors, Religious Activities and Education",
"Disc Jockeys",
"Dispatchers",
"Economists",
"Editors",
"Education Administrators",
"Electrical Engineers",
"Electricians",
"Elementary School Teachers",
"Emergency Management Directors",
"Emergency Medical Technicians and Paramedics",
"Environmental Engineers",
"Environmental Scientists and Specialists",
"Epidemiologists",
"Event Planners",
"Exercise Physiologists",
"Fashion Designers",
"Film and Video Editors",
"Financial Analysts",
"Financial Examiners",
"Financial Managers",
"Fire Inspectors and Investigators",
"Firefighters",
"Fitness Trainers and Aerobics Instructors",
"Flight Attendants",
"Floral Designers",
"Food Scientists and Technologists",
"Forensic Science Technicians",
"Fundraisers",
"Funeral Attendants",
"General and Operations Managers",
"Genetic Counselors",
"Geographers",
"Geoscientists",
"Glaziers",
"Graphic Designers",
"Hairdressers, Hairstylists, and Cosmetologists",
"Health and Safety Engineers",
"Healthcare Social Workers",
"Heating, Air Conditioning, and Refrigeration Mechanics",
"High School Teachers",
"Historians",
"Home Health and Personal Care Aides",
"Human Resources Managers",
"Human Resources Specialists",
"Hydrologists",
"Industrial Designers",
"Industrial Engineers",
"Industrial Machinery Mechanics",
"Information Security Analysts",
"Insurance Appraisers",
"Insurance Sales Agents",
"Insurance Underwriters",
"Interior Designers",
"Interpreters and Translators",
"Janitors and Cleaners",
"Jewelers and Precious Stone Workers",
"Journalists",
"Judges and Magistrates",
"Kindergarten Teachers",
"Landscape Architects",
"Lawyers",
"Librarians and Media Collections Specialists",
"Licensed Practical Nurses",
"Loan Officers",
"Locksmiths and Safe Repairers",
"Logisticians",
"Machine Learning Engineers",
"Machinists",
"Maids and Housekeeping Cleaners",
"Management Analysts",
"Marine Engineers and Naval Architects",
"Market Research Analysts",
"Marketing Managers",
"Marriage and Family Therapists",
"Massage Therapists",
"Mathematicians",
"Mechanical Engineers",
"Medical Assistants",
"Medical Records Specialists",
"Medical Scientists",
"Microbiologists",
"Middle School Teachers",
"Millwrights",
"Mining and Geological Engineers",
"Music Directors and Composers",
"Musicians and Singers",
"Network and Computer Systems Administrators",
"Nuclear Engineers",
"Nurse Anesthetists",
"Nurse Practitioners",
"Nursing Assistants",
"Occupational Health and Safety Specialists",
"Occupational Therapists",
"Opticians, Dispensing",
"Optometrists",
"Orthodontists",
"Paralegals and Legal Assistants",
"Parking Enforcement Workers",
"Pathologists",
"Pediatricians",
"Personal Financial Advisors",
"Pest Control Workers",
"Pharmacists",
"Pharmacy Technicians",
"Phlebotomists",
"Photographers",
"Physical Therapist Assistants",
"Physical Therapists",
"Physician Assistants",
"Physicians and Surgeons",
"Physicists",
"Pipefitters and Steamfitters",
"Plumbers",
"Podiatrists",
"Police Officers",
"Political Scientists",
"Postal Service Mail Carriers",
"Postsecondary Teachers",
"Preschool Teachers",
"Private Detectives and Investigators",
"Probation Officers",
"Producers and Directors",
"Product Managers",
"Property Managers",
"Prosthodontists",
"Psychiatric Technicians",
"Psychiatrists",
"Psychologists",
"Public Relations Managers",
"Public Relations Specialists",
"Purchasing Managers",
"Radiation Therapists",
"Radiologic Technologists and Technicians",
"Real Estate Agents and Brokers",
"Receptionists and Information Clerks",
"Recreation Workers",
"Recreational Therapists",
"Registered Nurses",
"Rehabilitation Counselors",
"Reporters and Correspondents",
"Respiratory Therapists",
"Restaurant Cooks",
"Roofers",
"Sales Engineers",
"Sales Managers",
"School Bus Drivers",
"School Psychologists",
"Security Guards",
"Sheet Metal Workers",
"Sheriffs and Deputy Sheriffs",
"Skincare Specialists",
"Social and Community Service Managers",
"Social Workers",
"Software Developers",
"Software Quality Assurance Analysts",
"Sound Engineering Technicians",
"Special Education Teachers",
"Speech-Language Pathologists",
"Statisticians",
"Structural Iron and Steel Workers",
"Substance Abuse Counselors",
"Surgeons",
"Survey Researchers",
"Surveyors",
"Tax Examiners and Collectors",
"Tax Preparers",
"Taxi Drivers and Chauffeurs",
"Technical Writers",
"Telecommunications Equipment Installers",
"Tellers",
"Training and Development Managers",
"Training and Development Specialists",
"Travel Agents",
"Urban and Regional Planners",
"UX Designers",
"Veterinarians",
"Veterinary Technologists and Technicians",
"Waiters and Waitresses",
"Water Treatment Plant Operators",
"Web Developers",
"Welders, Cutters, Solderers, and Brazers",
"Writers and Authors",
"Zoologists and Wildlife Biologists",
].map((name, i) => ({id: i, name}));
const professionSheetSnaps = ["355px"];
export function ProfessionsPicker() {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState("");
const [selected, setSelected] = useState(null);
const filtered = useMemo(() => {
if (!search) return PROFESSIONS;
const q = search.toLowerCase();
return PROFESSIONS.filter((p) => p.name.toLowerCase().includes(q));
}, [search]);
return (
{
setIsOpen(open);
if (!open) setSearch("");
}}
>
Choose Occupation
{filtered.length === 0 ? (
No occupations found.
) : (
{
const prof = PROFESSIONS.find((p) => p.id === Number(key));
if (prof) {
setSelected(prof.name);
setIsOpen(false);
}
}}
>
{(item) => (
{item.name}
)}
)}
Selected: {selected}
);
}
```
## Customization
Use `className` on compound parts or override the `sheet__*` BEM classes in `@layer components`. Preserve React Aria's scroll positioning rather than adding a separate translate-based entrance animation.
### Tailwind CSS
Apply Tailwind CSS utilities to individual parts with `className`.
```tsx
Open sheet
Details
Sheet content
```
### Global CSS
Use `@layer components` to customize the Sheet BEM classes.
```css
@layer components {
.sheet__dialog {
@apply border border-border;
}
.sheet__header {
@apply text-center;
}
.sheet__body {
@apply px-6 pb-6;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to keep component styles reusable and easy to customize.
### CSS Classes
#### Base Classes \[!toc]
* `.sheet__overlay` - Modal overlay container
* `.sheet__backdrop` - Backdrop behind the sheet
* `.sheet__content` - Sheet sizing and stacking wrapper
* `.sheet__dialog` - Dialog surface
* `.sheet__header` - Header section
* `.sheet__heading` - Dialog title
* `.sheet__body` - Content area that scrolls after full expansion
* `.sheet__footer` - Footer actions
* `.sheet__handle` - Draggable handle area
* `.sheet__handle-bar` - Default visual grip
* `.sheet__close-trigger` - Close button
#### Backdrop Variants \[!toc]
* `.sheet__backdrop--opaque` - Dimmed backdrop (default)
* `.sheet__backdrop--blur` - Blurred backdrop
* `.sheet__backdrop--transparent` - Transparent backdrop
Only the bottom-most sheet in a nested stack dims the page.
#### Placement Variants \[!toc]
* `.sheet__content--bottom`, `.sheet__dialog--bottom` - Bottom edge (default)
* `.sheet__content--top`, `.sheet__dialog--top` - Top edge
* `.sheet__content--left`, `.sheet__dialog--left` - Left edge
* `.sheet__content--right`, `.sheet__dialog--right` - Right edge
### Interactive States
* `[data-focus-visible="true"]` - Keyboard focus on the close button; native fallback uses `:focus-visible:not([data-focused])`
* `:hover`, `[data-hovered="true"]` - Close button hover
* `:disabled`, `[aria-disabled="true"]` - Disabled close button
* `[data-dragging]` - Handle drag in progress
* `[data-expanded]` - Fully expanded sheet; enables body scrolling
* `[data-has-descendants]` - Parent sheet with an open nested sheet
* `[data-detached]` - Inset spacing and rounded corners
* `[data-snap-points]` - Sheet configured with partial snap points
* `[data-entering]`, `[data-exiting]` - Opening and closing lifecycle states
* `[data-closed]` - Retained closed overlay; pointer interaction is disabled
### Data Attributes
The rendered parts expose `data-slot`: `sheet-overlay`, `sheet-backdrop`, `sheet-content`, `sheet-dialog`, `sheet-header`, `sheet-heading`, `sheet-body`, `sheet-footer`, `sheet-handle`, `sheet-handle-bar`, and `sheet-close-trigger`. The root, trigger, and close wrappers do not add DOM elements.
`Sheet.Content` also exposes `data-placement`, `data-detached`, and `data-snap-points`. React Aria supplies `data-position`, `data-expanded`, `data-stack-index`, `data-has-descendants`, `data-entering`, and `data-exiting` on applicable parts.
## API Reference
### Sheet / Sheet.Root / Sheet.NestedRoot
| Prop | Type | Default | Description |
| ---------------- | ---------------------------------------- | ---------- | ------------------------------------------------------ |
| `children` | `ReactNode` | — | Compound parts. |
| `isOpen` | `boolean` | — | Controlled visibility. |
| `defaultOpen` | `boolean` | `false` | Initial uncontrolled visibility. |
| `onOpenChange` | `(open: boolean) => void` | — | Called when a visibility change is requested. |
| `onClose` | `() => void` | — | Called when closing is requested. |
| `onAnimationEnd` | `(open: boolean) => void` | — | Called after entering or exiting finishes. |
| `placement` | `"bottom" \| "top" \| "left" \| "right"` | `"bottom"` | Physical screen edge. |
| `isDismissable` | `boolean` | `true` | Allow outside, Escape, and swipe dismissal. |
| `isDetached` | `boolean` | `false` | Inset the sheet from the screen edges. |
| `snapPoints` | `(number \| string)[]` | — | Partial resting positions; full expansion is implicit. |
`Sheet.NestedRoot` requires a parent Sheet. The `isNested` root flag is internal; use `NestedRoot` for nesting.
### Sheet.Trigger
| Prop | Type | Default | Description |
| ---------- | -------------- | ------- | ----------------------------------------------------------------- |
| `children` | `ReactElement` | — | One pressable child, such as Button. Place directly inside Sheet. |
### Sheet.Close
| Prop | Type | Default | Description |
| ---------- | -------------- | ------- | ------------------------------------------------------------------ |
| `children` | `ReactElement` | — | One pressable child; its onPress handler is composed with closing. |
### Sheet.Backdrop
| Prop | Type | Default | Description |
| ----------- | ------------------------------------- | ---------- | -------------------------------------------------------- |
| `children` | `ReactNode` | — | Content to render. |
| `className` | `string` | — | CSS classes. |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | Backdrop appearance. |
| `isExiting` | `boolean` | — | Retain content after closing until this flag is cleared. |
Other props inherit from React Aria SheetOverlay, excluding root-owned state, position, snap points, and `style`. Closing still releases modal focus containment and scroll locking. Clear `isExiting` when retained content is no longer needed.
### Sheet.Content
| Prop | Type | Default | Description |
| ----------- | -------------------------------------------------------- | ------- | ------------------------------------------------------- |
| `children` | `ReactNode \| ((values: SheetRenderProps) => ReactNode)` | — | Content or a function receiving React Aria sheet state. |
| `className` | `string` | — | CSS classes. |
Other props inherit from React Aria Sheet. HeroUI manages stacking animation and overscroll padding; `stackAnimation` and `overscrollPadding` cannot be overridden through this component.
### Sheet.Dialog
| Prop | Type | Default | Description |
| ------------------ | --------------------------- | ---------- | ---------------------------------------------------- |
| `children` | `ReactNode` | — | Content to render. |
| `className` | `string` | — | CSS classes. |
| `role` | `"dialog" \| "alertdialog"` | `"dialog"` | Accessible dialog role. |
| `aria-label` | `string` | — | Accessible name when no visible heading is provided. |
| `aria-labelledby` | `string` | — | ID of the element that labels the dialog. |
| `aria-describedby` | `string` | — | ID of the element describing the dialog. |
Other props and refs inherit from React Aria SheetContent.
### Sheet.Header
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | --------------- |
| `children` | `ReactNode` | — | Header content. |
| `className` | `string` | — | CSS classes. |
Supports standard div props, event handlers, and refs.
### Sheet.Heading
| Prop | Type | Default | Description |
| ----------- | ----------- | --------- | --------------------------------------- |
| `children` | `ReactNode` | — | Dialog title. |
| `className` | `string` | — | CSS classes. |
| `slot` | `string` | `"title"` | Associates the heading with the dialog. |
Other props and refs inherit from React Aria Heading.
### Sheet.Body
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------------------ |
| `children` | `ReactNode` | — | Scrollable body content. |
| `className` | `string` | — | CSS classes. |
Supports standard div props, event handlers, and refs.
### Sheet.Footer
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | --------------- |
| `children` | `ReactNode` | — | Footer actions. |
| `className` | `string` | — | CSS classes. |
Supports standard div props, event handlers, and refs.
### Sheet.Handle
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | -------------------------------------------------- |
| `children` | `ReactNode` | — | Custom grip; renders the default bar when omitted. |
| `className` | `string` | — | CSS classes. |
Supports standard div props, event handlers, and refs.
### Sheet.CloseTrigger
| Prop | Type | Default | Description |
| ------------ | --------------------------------------------------------- | --------- | ---------------------------------------------------- |
| `children` | `ReactNode \| ((values: ButtonRenderProps) => ReactNode)` | — | Custom content; defaults to the close icon. |
| `className` | `string` | — | CSS classes. |
| `isDisabled` | `boolean` | `false` | Disable the close button. |
| `aria-label` | `string` | `"Close"` | Accessible name; provide localized text when needed. |
| `onPress` | `(event: PressEvent) => void` | — | Called together with the sheet close action. |
Other props and refs inherit from CloseButton.
Named exports include `SheetRoot`, `SheetTrigger`, and the other parts, their props types, `SheetProps`, `SheetNestedRootProps`, `sheetVariants`, and `SheetVariants`. Compound prop types use indexed access, for example `Sheet["Props"]` and `Sheet["ContentProps"]`.
## Sheet and Drawer
Sheet uses React Aria's native scroll gestures, partial snap positions, and nested stacking. Drawer remains available with its existing API and behavior. Adding Sheet does not migrate or deprecate Drawer.
## Accessibility
* **Accessible name**: Label the dialog with `Sheet.Heading`, `aria-label`, or `aria-labelledby`.
* **Focus management**: Opening moves focus into the sheet and contains it within the active modal. Closing restores focus to the trigger; closing a nested sheet restores focus within its parent.
* **Keyboard**: Use `Tab` and `Shift+Tab` to move between controls. `Escape` closes the sheet when dismissal is enabled.
* **Close controls**: `Sheet.Close` and `Sheet.CloseTrigger` support keyboard activation, including when `isDismissable={false}`.
* **Scroll locking**: Page scrolling is locked while the modal is open. `Sheet.Body` scrolls after full expansion. Retained closed content does not keep the page locked.
* **Drag handle**: The handle is hidden from assistive technology and does not support keyboard dragging. Provide accessible close controls instead of relying solely on swipe gestures.
## Related Components
## Related Components
* **Drawer**: Slide-out panel for supplementary content and actions
* **Modal**: Displays content in a modal overlay
* **Button**: Allows a user to perform an action
# Toast
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/toast
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(overlays)/toast.mdx
> Display temporary notifications and messages to users with automatic dismissal and customizable placement
## Usage
```tsx
import { Toast, toast } from '@heroui/react';
```
```tsx
"use client";
import {Persons} from "@gravity-ui/icons";
import {Button, toast} from "@heroui/react";
export function Default() {
return (
{
const id = toast("You have been invited to join a team", {
actionProps: {
children: "Dismiss",
onPress: () => toast.close(id),
variant: "tertiary",
},
description: "Bob sent you an invitation to join HeroUI team",
indicator: ,
variant: "default",
});
}}
>
Show toast
);
}
```
## Anatomy
```tsx
```
## Examples
### Variants
```tsx
"use client";
import {HardDrive, Persons} from "@gravity-ui/icons";
import {Button, toast} from "@heroui/react";
export function Variants() {
return (
{
const id = toast("You have been invited to join a team", {
actionProps: {
children: "Dismiss",
onPress: () => toast.close(id),
variant: "tertiary",
},
description: "Bob sent you an invitation to join HeroUI team",
indicator: ,
variant: "default",
});
}}
>
Default toast
{
const id = toast.info("You have 2 credits left", {
actionProps: {children: "Upgrade", onPress: () => toast.close(id)},
description: "Get a paid plan for more credits",
});
}}
>
Accent toast
{
const id = toast.success("You have upgraded your plan", {
actionProps: {
children: "Billing",
className: "bg-success text-success-foreground",
onPress: () => toast.close(id),
},
description: "You can continue using HeroUI Chat",
});
}}
>
Success toast
{
const id = toast.warning("You have no credits left", {
actionProps: {
children: "Upgrade",
className: "bg-warning text-warning-foreground",
onPress: () => toast.close(id),
},
description: "Upgrade to a paid plan to continue",
});
}}
>
Warning toast
{
const id = toast.danger("Storage is full", {
actionProps: {children: "Remove", onPress: () => toast.close(id), variant: "danger"},
description:
"Remove files to release space. Adding more text to demonstrate longer content display",
indicator: ,
});
}}
>
Danger toast
);
}
```
### Placements
```tsx
"use client";
import type {ToastVariants} from "@heroui/react";
import {Button, Toast, ToastQueue} from "@heroui/react";
type Placement = NonNullable;
const placements = ["top start", "top", "top end", "bottom start", "bottom", "bottom end"] as const;
// Create a separate queue for each placement
const placementQueues = Object.fromEntries(
placements.map((p) => [p, new ToastQueue({maxVisibleToasts: 3})]),
) as Record;
export function Placements() {
const showToast = (placement: Placement) => {
placementQueues[placement].add({
description: "Event has been created",
title: "Event created",
variant: "default",
});
};
return (
{/* Render a ToastProvider for each placement */}
{placements.map((p) => (
))}
{placements.map((p) => (
showToast(p)}>
{p}
))}
);
}
```
### Expanded Stack
```tsx
"use client";
import {Button, Toast, ToastQueue} from "@heroui/react";
const queue = new ToastQueue();
export function Expanded() {
return (
{
queue.add({
title: "Simple message",
variant: "default",
});
setTimeout(() => {
queue.add({
title: "Operation completed",
variant: "success",
});
}, 400);
setTimeout(() => {
queue.add({
title: "New update available",
variant: "accent",
});
}, 800);
}}
>
Show 3 toasts
);
}
```
### Simple Toasts
```tsx
"use client";
import {Button, toast} from "@heroui/react";
export function Simple() {
return (
toast("Simple message")}>
Default
toast.success("Operation completed")}>
Success
toast.info("New update available")}>
Info
toast.warning("Please check your settings")}
>
Warning
toast.danger("Something went wrong")}>
Error
);
}
```
### Custom Indicators
```tsx
"use client";
import {Star} from "@gravity-ui/icons";
import {Button, toast} from "@heroui/react";
export function CustomIndicator() {
return (
toast("Custom icon indicator", {
indicator: ,
})
}
>
Custom indicator
);
}
```
### Custom Toast Rendering
```tsx
"use client";
import type {ToastContentValue} from "@heroui/react";
import {
Button,
Toast,
ToastContent,
ToastDescription,
ToastIndicator,
ToastQueue,
ToastTitle,
} from "@heroui/react";
export function CustomToast() {
const customQueue = new ToastQueue();
return (
{({toast: toastItem}) => {
const content = toastItem.content as ToastContentValue;
return (
{content.title ? (
{content.title}
) : null}
{content.description ? (
{content.description}
) : null}
);
}}
{
customQueue.add({
description: "This uses a custom render function",
title: "Custom layout toast",
variant: "default",
});
}}
>
Custom toast
);
}
```
### Promise & Loading
```tsx
"use client";
import {Button, toast} from "@heroui/react";
const uploadFile = (): Promise<{filename: string; size: number}> => {
return new Promise<{filename: string; size: number}>((resolve) => {
setTimeout(() => resolve({filename: "document.pdf", size: 1024}), 2000);
});
};
const createEvent = (): Promise => {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("Network error. Please try again.")), 2000);
});
};
const saveData = (): Promise<{count: number}> => {
return new Promise<{count: number}>((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve({count: 42});
} else {
reject(new Error("Failed to save data"));
}
}, 2000);
});
};
const fetchUser = (): Promise<{name: string; email: string}> => {
return new Promise<{name: string; email: string}>((resolve) => {
setTimeout(() => resolve({email: "john@example.com", name: "John Doe"}), 2000);
});
};
export function PromiseDemo() {
return (
{/* Promise API Section */}
Using toast.promise()
Automatically handles loading, success, and error states
{
toast.promise(uploadFile(), {
error: "Failed to upload file",
loading: "Uploading file...",
success: (data) => `File ${data.filename} uploaded (${data.size}KB)`,
});
}}
>
Upload file
{
toast.promise(createEvent(), {
error: (err) => err.message,
loading: "Creating event...",
success: "Event created",
});
}}
>
Create event (error)
{
toast.promise(saveData(), {
error: (err) => err.message,
loading: "Saving changes...",
success: (data) => `Saved ${data.count} items`,
});
}}
>
Save data (random)
{
toast.promise(fetchUser(), {
error: "Failed to fetch user",
loading: "Loading user...",
success: (data) => `Welcome back, ${data.name}!`,
});
}}
>
Fetch user
{/* Manual Loading Section */}
Manual Loading State
Manually control loading state with isLoading prop
{
const loadingId = toast("Uploading file...", {
description: "Please wait while we upload your file",
isLoading: true,
timeout: 0,
});
setTimeout(() => {
toast.update(loadingId, "File uploaded", {
description: "Your file has been uploaded successfully",
variant: "success",
});
}, 3000);
}}
>
Upload with loading
{
const loadingId = toast("Processing payment...", {
isLoading: true,
timeout: 0,
});
setTimeout(() => {
toast.update(loadingId, "Payment processed", {
description: "Your payment has been processed successfully",
variant: "success",
});
}, 2500);
}}
>
Payment processing
{
const loadingId = toast("Saving changes...", {
isLoading: true,
timeout: 0,
});
setTimeout(() => {
toast.update(loadingId, "Failed to save", {
description: "Please try again",
variant: "danger",
});
}, 2000);
}}
>
Loading to error
);
}
```
### Callbacks
```tsx
"use client";
import {Button, toast} from "@heroui/react";
import React from "react";
export function Callbacks() {
const [closedHistory, setClosedHistory] = React.useState>(
[],
);
const addToHistory = (message: string) => {
const time = new Date().toLocaleTimeString();
setClosedHistory((prev) => [{message, time}, ...prev].slice(0, 5));
};
return (
{/* Toast Buttons */}
toast("File saved", {
onClose: () => {
addToHistory("File saved (closed after 3 seconds)");
},
timeout: 3000,
})
}
>
Custom timeout (3s)
toast("Changes saved", {
onClose: () => {
addToHistory("Changes saved (closed after 10 seconds)");
},
timeout: 10000,
})
}
>
Custom timeout (10s)
toast.success("Event created", {
onClose: () => {
addToHistory("Event created (closed after default timeout)");
},
})
}
>
With onClose callback
toast("Important notification", {
description: "This toast will stay until dismissed",
onClose: () => {
addToHistory("Important notification (manually closed)");
},
timeout: 0,
})
}
>
Persistent toast
{/* Closed History Panel */}
Closed History
{closedHistory.length > 0 && (
setClosedHistory([])}
>
Clear
)}
{closedHistory.length === 0 ? (
No toasts closed yet. Try closing one above!
) : (
closedHistory.map((item, index) => (
{item.message}
({item.time})
))
)}
);
}
```
### Custom Queues
```tsx
"use client";
import {Button, Toast, ToastQueue} from "@heroui/react";
export function CustomQueue() {
const notificationQueue = new ToastQueue({maxVisibleToasts: 2});
const errorQueue = new ToastQueue({maxVisibleToasts: 3});
const successQueue = new ToastQueue({maxVisibleToasts: 1});
return (
{/* Notification Queue */}
{
notificationQueue.add({
description: "You have a new message",
title: "New notification",
variant: "default",
});
}}
>
Add notification (max 2)
{/* Error Queue */}
{
errorQueue.add({
description: "Failed to save changes",
title: "Error occurred",
variant: "danger",
});
}}
>
Add error (max 3)
{/* Success Queue */}
{
successQueue.add({
description: `Operation ${Date.now()}`,
title: "Success!",
variant: "success",
});
}}
>
Add success (max 1)
);
}
```
### Setup
Render the provider in the root of your app.
```tsx
import { Toast, Button, toast } from '@heroui/react';
function App() {
return (
toast("Simple message")}>
Show toast
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import type {ToastContentValue} from "@heroui/react";
import {
Button,
Toast,
ToastContent,
ToastDescription,
ToastIndicator,
ToastQueue,
ToastTitle,
} from "@heroui/react";
export function CustomStyles() {
const queue = new ToastQueue();
return (
{({toast: toastItem}) => {
const content = toastItem.content as ToastContentValue;
return (
{content.title ? (
{content.title}
) : null}
{content.description ? (
{content.description}
) : null}
);
}}
{
queue.add({
description: "Draft synced",
title: "Saved",
variant: "default",
});
}}
>
Show toast
);
}
```
### Global CSS
To customize the Toast component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.toast {
@apply rounded-xl shadow-lg;
}
.toast__content {
@apply gap-2;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Toast component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/toast.css)):
#### Base Classes \[!toc]
* `.toast` - Base toast container
* `.toast-region` - Toast region container
* `.toast__content` - Content wrapper for title and description
* `.toast__indicator` - Icon/indicator container
* `.toast__title` - Toast title text
* `.toast__description` - Toast description text
* `.toast__action` - Action button container
* `.toast__close-button` - Close button container
#### Variant Classes \[!toc]
* `.toast--default` - Default gray variant
* `.toast--accent` - Accent blue variant
* `.toast--success` - Success green variant
* `.toast--warning` - Warning yellow/orange variant
* `.toast--danger` - Danger red variant
### Interactive States
The component supports various states:
* **Frontmost**: `[data-frontmost]` - Applied to the topmost visible toast
* **Index**: `[data-index]` - Applied based on toast position in stack
* **Placement**: `[data-placement="*"]` - Applied based on toast region placement
* **Entering**: `[data-entering]` - Applied on first paint of a new toast
* **Exiting**: `[data-exiting]` - Applied while a closing toast plays its exit transition
* **Expanded**: `[data-expanded]` - Applied to every toast while the stack is expanded
* **Hidden**: `[data-hidden]` - Applied to toasts beyond `maxVisibleToasts`
## API Reference
### Toast.Provider
| Prop | Type | Default | Description |
| ------------------ | --------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `placement` | `"top start" \| "top" \| "top end" \| "bottom start" \| "bottom" \| "bottom end"` | `"bottom"` | Placement of the toast region |
| `gap` | `number` | `12` | The gap between toasts in pixels |
| `isExpanded` | `boolean` | `false` | Force the stack into its expanded layout (does not pause timers) |
| `maxVisibleToasts` | `number` | `3` | Maximum number of toasts to display at once |
| `hotkey` | `string[]` | `["altKey", "KeyT"]` | Hotkey that moves focus to the toast region and expands the stack. Modifiers match `KeyboardEvent` boolean properties (e.g. `"altKey"`), other keys match `event.code` (e.g. `"KeyT"`). Modifiers you leave out must be up, so `Alt`+`T` does not also fire on `Ctrl`+`Alt`+`T`. Pass `[]` to disable |
| `scaleFactor` | `number` | `0.05` | Scale factor for stacked toasts (0-1) |
| `width` | `number \| string` | `460` | Width of the toast in pixels or CSS value |
| `queue` | `ToastQueue` | - | Custom toast queue instance |
| `children` | `ReactNode \| ((props: {toast: QueuedToast}) => ReactNode)` | - | Custom render function or children |
| `className` | `string` | - | Additional CSS classes |
### Toast
| Prop | Type | Default | Description |
| ------------- | ------------------------------------------------------------- | ----------- | -------------------------------------------------- |
| `toast` | `QueuedToast` | - | Toast data from queue (required) |
| `variant` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Visual variant of the toast |
| `placement` | `ToastVariants["placement"]` | - | Placement (inherited from Provider) |
| `scaleFactor` | `number` | - | Scale factor (inherited from Provider) |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Toast content (ToastContent, ToastIndicator, etc.) |
### Toast.Content
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | --------------------------------------------------- |
| `children` | `ReactNode` | - | Content (typically ToastTitle and ToastDescription) |
| `className` | `string` | - | Additional CSS classes |
### Toast.Indicator
| Prop | Type | Default | Description |
| ----------- | -------------------------- | ------- | ------------------------------------------------ |
| `variant` | `ToastVariants["variant"]` | - | Variant for default icon |
| `children` | `ReactNode` | - | Custom indicator icon (defaults to variant icon) |
| `className` | `string` | - | Additional CSS classes |
### Toast.Title
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `children` | `ReactNode` | - | Title text |
| `className` | `string` | - | Additional CSS classes |
### Toast.Description
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `children` | `ReactNode` | - | Description text |
| `className` | `string` | - | Additional CSS classes |
### Toast.ActionButton
| Prop | Type | Default | Description |
| ------------------ | ----------- | ------- | ---------------------------------- |
| `children` | `ReactNode` | - | Action button content |
| `className` | `string` | - | Additional CSS classes |
| All `Button` props | - | - | Accepts all Button component props |
### Toast.CloseButton
| Prop | Type | Default | Description |
| ----------------------- | -------- | ------- | --------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| All `CloseButton` props | - | - | Accepts all CloseButton component props |
### ToastQueue
A `ToastQueue` manages the state for a ``. The state is stored outside React so you can trigger toasts from anywhere in your application.
#### Constructor Options
| Option | Type | Default | Description |
| ------------------ | -------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `exitDuration` | `number` | `300` | How long a closing toast stays mounted for its exit animation (0 removes immediately) |
| `maxVisibleToasts` | `number` | `3` | Maximum number of toasts to display at once (visual only) |
| `wrapUpdate` | `(fn: () => void) => void` | - | Function to wrap state updates (e.g. `document.startViewTransition`); by default updates are applied directly and animated with CSS transitions |
#### Methods
| Method | Parameters | Returns | Description |
| ----------- | ------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `add` | `(content: T, options?: ToastOptions)` | `string` | Add a toast to the queue, returns toast key |
| `update` | `(key: string, content: T, options?: {timeout?: number; onClose?: () => void})` | `boolean` | Update a toast in place, preserving its stack position; `timeout` restarts the countdown (0 keeps it open), omit it to keep the current countdown. Returns `false` if the toast no longer exists |
| `close` | `(key: string)` | `void` | Close a toast by its key |
| `pauseAll` | `()` | `void` | Pause all toast timers |
| `resumeAll` | `()` | `void` | Resume all toast timers |
| `clear` | `()` | `void` | Close all toasts (each toast animates out and fires its `onClose`) |
| `subscribe` | `(fn: () => void)` | `() => void` | Subscribe to queue changes, returns unsubscribe function |
### toast Function
The default `toast` function provides convenient methods for showing toasts:
```tsx
import { toast } from '@heroui/react';
// Basic toast (auto-dismisses after 4 seconds by default)
toast("Event has been created");
// Variant methods (also auto-dismiss after 4 seconds by default)
toast.success("File saved");
toast.info("New update available");
toast.warning("Please check your settings");
toast.danger("Something went wrong");
// With options. The returned id lets the action close its own toast.
const eventId = toast("Event has been created", {
description: "Your event has been scheduled for tomorrow",
variant: "default",
timeout: 5000, // Custom timeout: 5 seconds
onClose: () => console.log("Closed"),
actionProps: {
children: "View",
onPress: () => toast.close(eventId),
},
indicator: ,
});
// Update an existing toast in place (keeps its position in the stack).
// Options you omit are inherited, so pass `timeout` to start a countdown
// on a toast that was created persistent.
const id = toast("Saving…", { timeout: 0 });
toast.update(id, "Saved", { variant: "success", timeout: 4000 });
// Promise support (automatically shows loading spinner). The loading toast
// updates in place when the promise settles — same toast, same stack
// position; the auto-dismiss countdown starts at that point.
toast.promise(
uploadFile(),
{
loading: "Uploading file...",
success: (data) => `File ${data.filename} uploaded`,
error: "Failed to upload file",
}
);
// Manual loading state (persistent toast - no auto-dismiss)
const loadingId = toast("Creating event...", {
isLoading: true,
timeout: 0, // Persistent toast that doesn't auto-dismiss
});
// Later, update in place and start the auto-dismiss countdown
toast.update(loadingId, "Event created", { variant: "success", timeout: 4000 });
// Queue methods
toast.close(key);
toast.clear();
toast.pauseAll();
toast.resumeAll();
```
#### toast Options
| Option | Type | Default | Description |
| ------------- | ------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `title` | `ReactNode` | - | Toast title (first parameter for variant methods) |
| `description` | `ReactNode` | - | Optional description text |
| `variant` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Visual variant |
| `indicator` | `ReactNode` | - | Custom indicator icon (null to hide) |
| `actionProps` | `ButtonProps` | - | Props for action button |
| `isLoading` | `boolean` | `false` | Show loading spinner instead of indicator |
| `timeout` | `number` | `4000` | Auto-dismiss timeout in milliseconds. Defaults to 4000ms (4 seconds). Set to `0` for persistent toasts that don't auto-dismiss |
| `onClose` | `() => void` | - | Called when the toast is dismissed, as its exit animation starts |
#### toast.promise Options
| Option | Type | Default | Description |
| --------- | -------------------------------------------- | ------- | ------------------------------------------ |
| `loading` | `ReactNode` | - | Message shown while promise is pending |
| `success` | `ReactNode \| ((data: T) => ReactNode)` | - | Message shown on success (can be function) |
| `error` | `ReactNode \| ((error: Error) => ReactNode)` | - | Message shown on error (can be function) |
## Accessibility
Each toast implements the [WAI-ARIA alertdialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/), grouped inside a labeled landmark region:
* **Landmark region**: Toasts render in a labeled landmark reachable with `F6`; `Alt` + `T` moves focus straight to it. The hotkey is configurable via the `hotkey` prop and can be disabled by passing an empty array, and the region label can be overridden with `aria-label`
* **Keyboard**: `Tab` reaches the controls of visible toasts, `Escape` collapses the expanded stack, and dismissing a focused toast moves focus to the nearest remaining toast
* **Screen readers**: Each toast uses `role="alertdialog"` with its title and description linked; new toasts are announced automatically
* **Timers**: Auto-dismiss pauses while the stack is hovered or focused, and while the page is in a background tab
## Related Components
## Related Components
* **Button**: Allows a user to perform an action
* **Alert**: Display important messages and notifications
* **CloseButton**: Button for dismissing overlays
# Tooltip
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/tooltip
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(overlays)/tooltip.mdx
> Displays informative text when users hover over or focus on an element
## Usage
```tsx
import { Tooltip } from '@heroui/react';
```
```tsx
import {CircleInfo} from "@gravity-ui/icons";
import {Button, Tooltip} from "@heroui/react";
export function TooltipBasic() {
return (
Hover me
This is a tooltip
More information
);
}
```
## Anatomy
```tsx
import { Tooltip, Button } from '@heroui/react';
export default () => (
Hover for tooltip
Helpful information about this element
)
```
## Examples
### Placement
```tsx
import {Button, Tooltip} from "@heroui/react";
export function TooltipPlacement() {
return (
Top
Top placement
Left
Left placement
Hover buttons
Right
Right placement
Bottom
Bottom placement
);
}
```
### With Arrow
```tsx
import {Button, Tooltip} from "@heroui/react";
export function TooltipWithArrow() {
return (
With Arrow
Tooltip with arrow indicator
Custom Offset
Custom offset from trigger
);
}
```
### Custom Triggers
```tsx
import {CircleCheckFill, CircleQuestion} from "@gravity-ui/icons";
import {Avatar, Chip, Tooltip} from "@heroui/react";
export function TooltipCustomTrigger() {
return (
JD
Jane Doe
jane@example.com
Active
Jane is currently online
Help Information
This is a helpful tooltip with more detailed information about this feature.
);
}
```
### Render Function
```tsx
"use client";
import {CircleInfo} from "@gravity-ui/icons";
import {Button, Tooltip} from "@heroui/react";
export function RenderFunction() {
return (
Hover me
}>
This is a tooltip
}>
More information
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Button, Tooltip} from "@heroui/react";
export function CustomStyles() {
return (
Share link
Copied to clipboard
);
}
```
### Global CSS
To customize the Tooltip component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.tooltip {
@apply rounded-xl shadow-lg;
}
.tooltip__trigger {
@apply cursor-help;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### Global Delay Configuration
The default theme sets the opening delay to `1500ms` and the closing delay to `500ms`. Override these CSS variables in your global stylesheet:
```css
:root {
--tooltip-delay: 1500ms;
--tooltip-close-delay: 500ms;
}
.light, [data-theme="light"] {
--tooltip-delay: 1200ms;
}
.dark, [data-theme="dark"] {
--tooltip-close-delay: 300ms;
}
```
Values support CSS time units such as `ms` and `s`. The `delay` and `closeDelay` props on an individual Tooltip override these global settings.
### CSS Classes
The Tooltip component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tooltip.css)):
#### Base Classes \[!toc]
* `.tooltip` - Base tooltip styles with animations
* `.tooltip__trigger` - Trigger element styles
### Interactive States
The component supports animation states:
* **Entering**: `[data-entering]` - Applied during tooltip appearance
* **Exiting**: `[data-exiting]` - Applied during tooltip disappearance
* **Placement**: `[data-placement="*"]` - Applied based on tooltip position
## API Reference
### Tooltip
| Prop | Type | Default | Description |
| --------------------- | -------------------- | ---------------------- | --------------------------------------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | Trigger element and content |
| `delay` | `number` | `1500` or CSS variable | Delay in milliseconds before showing tooltip; configured globally with `--tooltip-delay` |
| `closeDelay` | `number` | `500` or CSS variable | Delay in milliseconds before hiding tooltip; configured globally with `--tooltip-close-delay` |
| `trigger` | `"hover" \| "focus"` | `"hover"` | How the tooltip is triggered |
| `isDisabled` | `boolean` | `false` | Whether the tooltip is disabled |
| `shouldSkipAnimation` | `boolean` | `false` | Whether to skip the enter and exit animations when moving quickly from one tooltip to another |
### Tooltip.Content
| Prop | Type | Default | Description |
| ----------- | -------------------------------------------------------------------------- | ------------------ | ---------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | Content to display in the tooltip |
| `showArrow` | `boolean` | `false` | Whether to show the arrow indicator |
| `offset` | `number` | `3` (7 with arrow) | Distance from the trigger element |
| `placement` | `"top" \| "bottom" \| "left" \| "right"` (and variants) | `"top"` | Placement of the tooltip |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Tooltip.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | --------------------------------- |
| `children` | `React.ReactNode` | - | Element that triggers the tooltip |
| `className` | `string` | - | Additional CSS classes |
### Tooltip.Arrow
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- |
| `children` | `React.ReactNode` | - | Custom arrow element |
| `className` | `string` | - | Additional CSS classes |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
## Related Components
## Related Components
* **Button**: Allows a user to perform an action
* **Popover**: Displays content in context with a trigger
# Autocomplete
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/autocomplete
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(pickers)/autocomplete.mdx
> An autocomplete combines a select with filtering, allowing users to search and select from a list of options
## Usage
```tsx
import { Autocomplete, useFilter } from "@heroui/react";
```
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export default function Default() {
const {contains} = useFilter({sensitivity: "base"});
const [selectedKeys, setSelectedKeys] = useState([]);
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
States to Visit
{({defaultChildren, isPlaceholder, state}: any) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item: any) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey: Key) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
No results found }>
{items.map((item) => (
{item.name}
))}
);
}
```
## Anatomy
```tsx
import {Autocomplete, Label, Description, SearchField, ListBox} from "@heroui/react";
export default () => (
);
```
## Examples
### Variants
The Autocomplete 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 type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function Variants() {
const [selectedKey1, setSelectedKey1] = useState(null);
const [selectedKey2, setSelectedKey2] = useState(null);
const [selectedKeys1, setSelectedKeys1] = useState([]);
const [selectedKeys2, setSelectedKeys2] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "option1", name: "Option 1"},
{id: "option2", name: "Option 2"},
{id: "option3", name: "Option 3"},
{id: "option4", name: "Option 4"},
];
const onRemoveTags1 = (keys: Set) => {
setSelectedKeys1((prev) => prev.filter((key) => !keys.has(key)));
};
const onRemoveTags2 = (keys: Set) => {
setSelectedKeys2((prev) => prev.filter((key) => !keys.has(key)));
};
return (
Single Select Variants
Primary variant
No results found }>
{items.map((item) => (
{item.name}
))}
Secondary variant
No results found }>
{items.map((item) => (
{item.name}
))}
Multiple Select Variants
setSelectedKeys1(keys as Key[])}
>
Primary variant
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
No results found }>
{items.map((item) => (
{item.name}
))}
setSelectedKeys2(keys as Key[])}
>
Secondary variant
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
No results found }>
{items.map((item) => (
{item.name}
))}
);
}
```
### Full Width
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Surface,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function FullWidth() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
State
No results found }>
{items.map((item) => (
{item.name}
))}
);
}
```
### With Description
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function WithDescription() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
State
No results found }>
{items.map((item) => (
{item.name}
))}
Select your state of residence
);
}
```
### Required
```tsx
"use client";
import {
Autocomplete,
Button,
EmptyState,
FieldError,
Form,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
export function Required() {
const onSubmit = (e: React.SyntheticEvent) => {
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!");
};
const {contains} = useFilter({sensitivity: "base"});
const states = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
const countries = [
{id: "usa", name: "United States"},
{id: "canada", name: "Canada"},
{id: "mexico", name: "Mexico"},
{id: "uk", name: "United Kingdom"},
{id: "france", name: "France"},
{id: "germany", name: "Germany"},
];
return (
State
No results found }>
{states.map((state) => (
{state.name}
))}
Country
No results found }>
{countries.map((country) => (
{country.name}
))}
Submit
);
}
```
### Disabled
```tsx
"use client";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
export function Disabled() {
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
const countries = [
{id: "argentina", name: "Argentina"},
{id: "venezuela", name: "Venezuela"},
{id: "japan", name: "Japan"},
{id: "france", name: "France"},
{id: "italy", name: "Italy"},
{id: "spain", name: "Spain"},
];
return (
State
No results found }>
{items.map((item) => (
{item.name}
))}
Countries to Visit
No results found }>
{countries.map((country) => (
{country.name}
))}
);
}
```
### With Disabled Options
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {useState} from "react";
export function WithDisabledOptions() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
return (
Animal
No results found }>
Dog
Cat
Bird
Kangaroo
Elephant
Tiger
);
}
```
### Allows Empty Collection
The `allowsEmptyCollection` prop enables the autocomplete to function even when there are no items in the collection. This is useful for scenarios where the list might be empty initially or when all items are filtered out.
```tsx
"use client";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
export function AllowsEmptyCollection() {
const {contains} = useFilter({sensitivity: "base"});
return (
State
No results found } />
);
}
```
### With Sections
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Header,
Label,
ListBox,
SearchField,
Separator,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function WithSections() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
return (
Country
No results found }>
United States
Canada
Mexico
United Kingdom
France
Germany
Spain
Italy
Japan
China
India
South Korea
);
}
```
### Multiple Select
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function MultipleSelect() {
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "florida", name: "Florida"},
{id: "new-york", name: "New York"},
{id: "illinois", name: "Illinois"},
{id: "pennsylvania", name: "Pennsylvania"},
];
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
States
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
No results found }>
{items.map((item) => (
{item.name}
))}
);
}
```
In multiple mode, a bare ` ` renders a copy of each selected item's
`ListBox.Item` children in the trigger, joined with locale-aware separators. Text-only items read
as plain text, but visual content such as checkboxes or avatars appears once per selected item,
and render function children are always called with `isSelected: false`.
To display selected values as plain text, use the `selectedText` render prop, which joins the
`textValue` of each selected item:
```tsx
{({isPlaceholder, defaultChildren, selectedText}) =>
isPlaceholder ? defaultChildren : selectedText
}
```
For richer layouts such as removable tags, render your own content from `state.selectedItems` as
shown in the example above.
### Controlled
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const states = [
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "florida", name: "Florida"},
{id: "new-york", name: "New York"},
{id: "illinois", name: "Illinois"},
{id: "pennsylvania", name: "Pennsylvania"},
];
const [state, setState] = useState("california");
const {contains} = useFilter({sensitivity: "base"});
const selectedState = states.find((s) => s.id === state);
return (
State (controlled)
No results found }>
{states.map((state) => (
{state.name}
))}
Selected: {selectedState?.name || "None"}
);
}
```
### Controlled Multiple
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {useState} from "react";
export function ControlledMultiple() {
const [selected, setSelected] = useState(["california", "texas"]);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "florida", name: "Florida"},
{id: "new-york", name: "New York"},
{id: "illinois", name: "Illinois"},
{id: "pennsylvania", name: "Pennsylvania"},
];
return (
setSelected(keys as Key[])}
>
States
No results found }>
{items.map((item) => (
{item.name}
))}
Selected: {selected.length > 0 ? selected.join(", ") : "None"}
);
}
```
### Controlled Open State
```tsx
"use client";
import {
Autocomplete,
Button,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function ControlledOpenState() {
const [isOpen, setIsOpen] = useState(false);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
State
No results found }>
{items.map((item) => (
{item.name}
))}
setIsOpen(!isOpen)}>{isOpen ? "Close" : "Open"} Autocomplete
Autocomplete is {isOpen ? "open" : "closed"}
);
}
```
### Asynchronous Filtering
```tsx
"use client";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, Spinner} from "@heroui/react";
import {useAsyncList} from "@react-stately/data";
import {cn} from "tailwind-variants";
interface Character {
name: string;
}
export function AsynchronousFiltering() {
const list = useAsyncList({
async load({filterText, signal}) {
const res = await fetch(`https://swapi.py4e.com/api/people/?search=${filterText}`, {
signal,
});
const json = await res.json();
return {
items: json.results,
};
},
});
return (
Search a Star Wars characters
No results found }
>
{(item: Character) => (
{item.name}
)}
);
}
```
### Custom Indicator
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function CustomIndicator() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
State
No results found }>
{items.map((item) => (
{item.name}
))}
);
}
```
### Custom Value
You can customize the displayed value using render props:
```tsx
"use client";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
export function CustomValue() {
const {contains} = useFilter({sensitivity: "base"});
const currencies = [
{code: "USD", id: "usd", name: "US Dollar", symbol: "$"},
{code: "EUR", id: "eur", name: "Euro", symbol: "€"},
{code: "GBP", id: "gbp", name: "British Pound", symbol: "£"},
{code: "JPY", id: "jpy", name: "Japanese Yen", symbol: "¥"},
{code: "CHF", id: "chf", name: "Swiss Franc", symbol: "₣"},
];
return (
Currency
{({defaultChildren, isPlaceholder, state}) => {
const selectedCurrency = currencies.find(
(currency) => currency.id === state.selectedItems[0]?.key,
);
if (isPlaceholder || !selectedCurrency) {
return defaultChildren;
}
return (
{selectedCurrency.symbol}
{selectedCurrency.code}
{selectedCurrency.name}
);
}}
No results found }>
{currencies.map((currency) => (
{currency.code}
{currency.name}
))}
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Surface,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function OnSurface() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
State
No results found }>
{items.map((item) => (
{item.name}
))}
);
}
```
### Virtualization
Autocomplete 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 type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
ListLayout,
SearchField,
Virtualizer,
useFilter,
} from "@heroui/react";
import {useMemo, useState} from "react";
interface User {
email: string;
id: number;
name: string;
}
function generateUsers(n: number): User[] {
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",
];
const users: User[] = [];
for (let i = 0; i < n; 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,
});
}
return users;
}
export function Virtualization() {
const [selectedKey, setSelectedKey] = useState(null);
const [searchQuery, setSearchQuery] = useState("");
const {contains} = useFilter({sensitivity: "base"});
const allUsers = useMemo(() => generateUsers(1000), []);
const filteredUsers = useMemo(() => {
if (!searchQuery) return allUsers;
return allUsers.filter(
(user) => contains(user.name, searchQuery) || contains(user.email, searchQuery),
);
}, [allUsers, contains, searchQuery]);
return (
User
No results found }
>
{(user) => (
{user.name}
{user.email}
)}
);
}
```
## Advanced Examples
### User Selection
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Avatar,
AvatarFallback,
AvatarImage,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function UserSelection() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
return (
User
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItems = state.selectedItems;
if (selectedItems.length > 1) {
return `${selectedItems.length} users selected`;
}
const selectedItem = users.find((user) => user.id === selectedItems[0]?.key);
if (!selectedItem) {
return defaultChildren;
}
return (
{selectedItem.fallback}
{selectedItem.name}
);
}}
No results found }>
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### User Selection Multiple
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Avatar,
AvatarFallback,
AvatarImage,
Description,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function UserSelectionMultiple() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
Users
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const selectedItem = users.find((user) => user.id === selectedItemKey);
if (!selectedItem) {
return null;
}
return (
{selectedItem.fallback}
{selectedItem.name}
);
})}
);
}}
No results found }>
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### Location Search
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
interface City {
name: string;
country: string;
}
export function LocationSearch() {
const allCities: City[] = [
{country: "USA", name: "New York"},
{country: "USA", name: "Los Angeles"},
{country: "USA", name: "Chicago"},
{country: "UK", name: "London"},
{country: "France", name: "Paris"},
{country: "Japan", name: "Tokyo"},
{country: "Australia", name: "Sydney"},
{country: "Canada", name: "Toronto"},
{country: "Germany", name: "Berlin"},
{country: "Spain", name: "Madrid"},
];
const [selectedKey, setSelectedKey] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const {contains} = useFilter({sensitivity: "base"});
// Simulate async filtering
const customFilter = (text: string, inputValue: string) => {
if (!inputValue) return true;
setIsLoading(true);
setTimeout(() => setIsLoading(false), 300);
return contains(text, inputValue);
};
return (
City
(
{isLoading ? "Searching..." : "No cities found"}
)}
>
{allCities.map((city) => (
{city.name}
{city.country}
))}
);
}
```
### Tag Group Selection
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function TagGroupSelection() {
const tags = [
{id: "react", name: "React"},
{id: "typescript", name: "TypeScript"},
{id: "javascript", name: "JavaScript"},
{id: "nodejs", name: "Node.js"},
{id: "python", name: "Python"},
{id: "vue", name: "Vue"},
{id: "angular", name: "Angular"},
{id: "nextjs", name: "Next.js"},
];
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
Tags
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const tag = tags.find((t) => t.id === selectedItemKey);
if (!tag) return null;
return (
{tag.name}
);
})}
);
}}
No tags found }>
{tags.map((tag) => (
{tag.name}
))}
);
}
```
### Email Recipients
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function EmailRecipients() {
const emails = [
{email: "alice@example.com", id: "alice@example.com", name: "Alice Johnson"},
{email: "bob@example.com", id: "bob@example.com", name: "Bob Smith"},
{email: "charlie@example.com", id: "charlie@example.com", name: "Charlie Brown"},
{email: "diana@example.com", id: "diana@example.com", name: "Diana Prince"},
{email: "eve@example.com", id: "eve@example.com", name: "Eve Wilson"},
];
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
To
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const email = emails.find((e) => e.id === selectedItemKey);
if (!email) return null;
return (
{email.email}
);
})}
);
}}
No recipients found }>
{emails.map((email) => (
{email.name}
{email.email}
))}
);
}
```
## Customization
### Tailwind CSS
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
const TEAMMATES = [
{id: "sarah", name: "Sarah Chen", role: "Product Design"},
{id: "marcus", name: "Marcus Lee", role: "Engineering"},
{id: "priya", name: "Priya Patel", role: "Data Science"},
{id: "jordan", name: "Jordan Kim", role: "Customer Success"},
{id: "alex", name: "Alex Rivera", role: "Marketing"},
] as const;
export function CustomStyles() {
const {contains} = useFilter({sensitivity: "base"});
const [selectedKeys, setSelectedKeys] = useState([]);
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys((keys ?? []) as Key[])}
>
Assignees
People who will be notified when this task updates.
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const teammate = TEAMMATES.find((member) => member.id === selectedItemKey);
if (!teammate) return null;
return (
{teammate.name}
);
})}
);
}}
No teammates found }
>
{TEAMMATES.map((teammate) => (
{teammate.name}
{teammate.role}
))}
);
}
```
### Global CSS
To customize the Autocomplete component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.autocomplete {
@apply flex flex-col gap-1;
}
.autocomplete__trigger {
@apply rounded-lg border border-border bg-surface p-2;
}
.autocomplete__value {
@apply text-current;
}
.autocomplete__clear-button {
@apply text-muted hover:text-foreground;
}
.autocomplete__indicator {
@apply text-muted;
}
.autocomplete__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Autocomplete component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/autocomplete.css)):
#### Base Classes \[!toc]
* `.autocomplete` - Base autocomplete container
* `.autocomplete__trigger` - The button that triggers the autocomplete
* `.autocomplete__value` - The displayed value or placeholder
* `.autocomplete__clear-button` - The clear button that removes the selected value
* `.autocomplete__indicator` - The dropdown indicator icon
* `.autocomplete__popover` - The popover container
* `.autocomplete__filter` - The filter wrapper
#### Variant Classes \[!toc]
* `.autocomplete--primary` - Primary variant with shadow (default)
* `.autocomplete--secondary` - Secondary variant without shadow, suitable for use in surfaces
#### State Classes \[!toc]
* `.autocomplete[data-invalid="true"]` - Invalid state
* `.autocomplete__trigger[data-focus-visible="true"]` - Focused trigger state
* `.autocomplete__trigger[data-disabled="true"]` - Disabled trigger state
* `.autocomplete__value[data-placeholder="true"]` - Placeholder state
* `.autocomplete__clear-button[data-empty="true"]` - Clear button hidden when no selection
* `.autocomplete__indicator[data-open="true"]` - Open indicator 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 `[data-disabled="true"]` on autocomplete
* **Open**: `[data-open="true"]` on indicator
## API Reference
### Autocomplete
| Prop | Type | Default | Description |
| ----------------------- | --------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `placeholder` | `string` | `'Select an item'` | Temporary text that occupies the autocomplete when it is empty |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | Whether single or multiple selection is enabled |
| `allowsEmptyCollection` | `boolean` | `false` | Whether the autocomplete allows an empty collection. When true, the autocomplete can function even with no items. |
| `isOpen` | `boolean` | - | Sets the open state of the popover (controlled) |
| `defaultOpen` | `boolean` | - | Sets the default open state of the popover (uncontrolled) |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Handler called when the open state changes |
| `disabledKeys` | `Iterable` | - | Keys of disabled items |
| `isDisabled` | `boolean` | - | Whether the autocomplete is disabled |
| `value` | `Key \| Key[] \| null` | - | Current value (controlled) |
| `defaultValue` | `Key \| Key[] \| null` | - | Default value (uncontrolled) |
| `onChange` | `(value: Key \| Key[] \| null) => void` | - | Handler called when the value changes |
| `isRequired` | `boolean` | - | Whether user input is required |
| `isInvalid` | `boolean` | - | Whether the autocomplete value is invalid |
| `name` | `string` | - | The name of the input, used when submitting an HTML form |
| `fullWidth` | `boolean` | `false` | Whether the autocomplete 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. |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Autocomplete content or render function |
### Autocomplete.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------------------------- | ------- | ---------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Trigger content or render function |
### Autocomplete.Value
| Prop | Type | Default | Description |
| ----------- | ----------------------------- | ------- | -------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Value content or render function |
### Autocomplete.Indicator
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------------------ |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator content |
### Autocomplete.ClearButton
| Prop | Type | Default | Description |
| ----------- | ------------------------------ | ------- | ------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `onClick` | `(e: MouseEvent) => void` | - | Handler called when button is clicked |
| `ref` | `RefObject` | - | Ref to the clear button element |
### Autocomplete.Popover
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------------------------------------ |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | Placement of the popover relative to the trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Content children |
### Autocomplete.Filter
| Prop | Type | Default | Description |
| --------------- | ------------------------------------------ | ------- | ---------------------------------------- |
| `filter` | `(text: string, input: string) => boolean` | - | Custom filter function |
| `inputValue` | `string` | - | Controlled input value |
| `onInputChange` | `(value: string) => void` | - | Handler called when input value changes |
| `children` | `ReactNode` | - | Filter content (SearchField and ListBox) |
### useFilter Hook
The `useFilter` hook from React Aria provides filtering functions for autocomplete functionality.
```tsx
import {useFilter} from "@heroui/react";
const {contains} = useFilter({sensitivity: "base"});
...
...
```
**Options:**
| Option | Type | Default | Description |
| ------------- | ------------------------------------------- | -------- | ------------------------------- |
| `sensitivity` | `"base" \| "accent" \| "case" \| "variant"` | `"base"` | Locale sensitivity for matching |
**Returns:**
| Function | Type | Description |
| ------------ | ------------------------------------------------ | ------------------------------------------------------ |
| `contains` | `(string: string, substring: string) => boolean` | Returns whether a string contains a given substring |
| `startsWith` | `(string: string, substring: string) => boolean` | Returns whether a string starts with a given substring |
| `endsWith` | `(string: string, substring: string) => boolean` | Returns whether a string ends with a given substring |
### Render Props
When using render functions with Autocomplete.Value, these values are provided:
| Prop | Type | Description |
| ----------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `defaultChildren` | `ReactNode` | The default rendered value. In multiple mode this is a copy of each selected item's children joined with locale-aware separators |
| `isPlaceholder` | `boolean` | Whether the value is a placeholder |
| `state` | `SelectState` | The state of the autocomplete. Use `state.selectedItems` for the selected collection nodes |
| `selectedItems` | `(T \| null)[]` | The values of the currently selected items |
| `selectedText` | `string` | The `textValue` of the selected items joined with locale-aware separators |
## Accessibility
The Autocomplete component implements the ARIA select pattern with filtering and provides:
* Full keyboard navigation support
* Screen reader announcements for selection changes
* Proper focus management
* Support for disabled states
* Search functionality with filtering
* HTML form integration
For more information, see the [React Aria Select documentation](https://react-spectrum.adobe.com/react-aria/Select.html).
## Related Components
## Related Components
* **Listbox**: Scrollable list of selectable items
* **Popover**: Displays content in context with a trigger
* **Input**: Single-line text input built on React Aria
# ComboBox
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/combo-box
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(pickers)/combo-box.mdx
> A combo box combines a text input with a listbox, allowing users to filter a list of options to items matching a query
## Usage
```tsx
import { ComboBox } from '@heroui/react';
```
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function Default() {
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
);
}
```
## Anatomy
```tsx
import { ComboBox, Input, Label, Description, Header, ListBox, Separator } from '@heroui/react';
export default () => (
{/* Displays the selected values, primarily used for multiple selection */}
)
```
## Examples
### Full Width
```tsx
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function FullWidth() {
return (
Favorite Animal
Aardvark
Cat
Dog
);
}
```
### With Description
```tsx
"use client";
import {ComboBox, Description, Input, Label, ListBox} from "@heroui/react";
export function WithDescription() {
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
Search and select your favorite animal
);
}
```
### Required
```tsx
"use client";
import {Button, ComboBox, FieldError, Form, Input, Label, ListBox} from "@heroui/react";
export function Required() {
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert("Form submitted successfully!");
};
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
Submit
);
}
```
### Disabled
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function Disabled() {
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
);
}
```
### With Disabled Options
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function WithDisabledOptions() {
return (
Animal
Dog
Cat
Bird
Kangaroo
Elephant
Tiger
);
}
```
### With Sections
```tsx
"use client";
import {ComboBox, Header, Input, Label, ListBox, Separator} from "@heroui/react";
export function WithSections() {
return (
Country
United States
Canada
Mexico
United Kingdom
France
Germany
Spain
Italy
Japan
China
India
South Korea
);
}
```
### Controlled
```tsx
"use client";
import type {Key} from "@heroui/react";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const animals = [
{
id: "cat",
name: "Cat",
},
{
id: "dog",
name: "Dog",
},
{
id: "bird",
name: "Bird",
},
{
id: "fish",
name: "Fish",
},
{
id: "hamster",
name: "Hamster",
},
];
const [selectedKey, setSelectedKey] = useState("cat");
const selectedAnimal = animals.find((a) => a.id === selectedKey);
return (
);
}
```
### Controlled Input Value
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
import {useState} from "react";
export function ControlledInputValue() {
const [inputValue, setInputValue] = useState("");
return (
);
}
```
### Asynchronous Loading
```tsx
"use client";
import {
Collection,
ComboBox,
EmptyState,
Input,
Label,
ListBox,
ListBoxLoadMoreItem,
Spinner,
} from "@heroui/react";
import {useAsyncList} from "@react-stately/data";
interface Character {
name: string;
}
export function AsynchronousLoading() {
const list = useAsyncList({
async load({cursor, filterText, signal}) {
if (cursor) {
cursor = cursor.replace(/^http:\/\//i, "https://");
}
const res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {
signal,
});
const json = await res.json();
return {
cursor: json.next,
items: json.results,
};
},
});
return (
Pick a Character
}>
{(item) => (
{item.name}
)}
Loading more...
);
}
```
### Default Selected Key
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function DefaultSelectedKey() {
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
);
}
```
### Allows Custom Value
```tsx
"use client";
import {ComboBox, Description, Input, Label, ListBox} from "@heroui/react";
export function AllowsCustomValue() {
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
You can type any animal name, even if it's not in the list
);
}
```
### Custom Indicator
```tsx
"use client";
import {ChevronsExpandVertical} from "@gravity-ui/icons";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function CustomIndicator() {
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
);
}
```
### Custom Value
```tsx
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
ComboBox,
Description,
Input,
Label,
ListBox,
} from "@heroui/react";
export function CustomValue() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
return (
User
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### Custom Filtering
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function CustomFiltering() {
const animals = [
{id: "cat", name: "Cat"},
{id: "dog", name: "Dog"},
{id: "bird", name: "Bird"},
{id: "fish", name: "Fish"},
{id: "hamster", name: "Hamster"},
];
return (
{
if (!inputValue) return true;
return text.toLowerCase().includes(inputValue.toLowerCase());
}}
>
Animal (custom filter)
{animals.map((animal) => (
{animal.name}
))}
);
}
```
### Render Function
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function RenderFunction() {
return (
}>
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
);
}
```
### Menu Trigger
Use the `menuTrigger` prop to control when the popover opens:
* `focus` (default): popover opens when the user focuses the input
* `input`: popover opens when the user edits the input text
* `manual`: popover only opens when the user presses the trigger button or uses the arrow keys
```tsx
"use client";
import {ComboBox, Description, Input, Label, ListBox} from "@heroui/react";
export function MenuTrigger() {
return (
);
}
```
### Form Value
Use the `formValue` prop to control whether the selected item's key or text is submitted in forms. `key` is the default. When `allowsCustomValue` is true, the text is always submitted.
```tsx
"use client";
import {
Button,
ComboBox,
Description,
FieldError,
Form,
Input,
Label,
ListBox,
} from "@heroui/react";
import {useState} from "react";
const animals = [
{id: "aardvark", name: "Aardvark"},
{id: "cat", name: "Cat"},
{id: "dog", name: "Dog"},
{id: "kangaroo", name: "Kangaroo"},
{id: "panda", name: "Panda"},
{id: "snake", name: "Snake"},
];
export function FormValue() {
const [message, setMessage] = useState(null);
return (
{
e.preventDefault();
const formData = new FormData(e.currentTarget);
setMessage(`Key: ${formData.get("animal")}. Text: ${formData.get("animal-text")}.`);
}}
>
Animal
{animals.map((animal) => (
{animal.name}
))}
Submits the selected key
Animal (text)
{animals.map((animal) => (
{animal.name}
))}
Submits the selected text
Submit
{!!message && {message}
}
);
}
```
### Validation Behavior
Use the `validationBehavior` prop to control how validation is displayed:
* `native` (default): blocks form submission when the value is missing or invalid
* `aria`: shows errors in realtime and does not block submission
```tsx
"use client";
import {
Button,
ComboBox,
Description,
FieldError,
Form,
Input,
Label,
ListBox,
} from "@heroui/react";
import {useState} from "react";
const animals = [
{id: "aardvark", name: "Aardvark"},
{id: "cat", name: "Cat"},
{id: "dog", name: "Dog"},
{id: "kangaroo", name: "Kangaroo"},
{id: "panda", name: "Panda"},
{id: "snake", name: "Snake"},
];
export function ValidationBehavior() {
const [nativeMessage, setNativeMessage] = useState(null);
const [ariaMessage, setAriaMessage] = useState(null);
return (
);
}
```
### Custom Validation
Use the `validate` prop to return an error message when the value is invalid, or `true` when it is valid.
```tsx
"use client";
import {Button, ComboBox, FieldError, Form, Input, Label, ListBox} from "@heroui/react";
const animals = [
{id: "aardvark", name: "Aardvark"},
{id: "cat", name: "Cat"},
{id: "dog", name: "Dog"},
{id: "kangaroo", name: "Kangaroo"},
{id: "panda", name: "Panda"},
{id: "snake", name: "Snake"},
];
export function CustomValidation() {
return (
{
e.preventDefault();
alert("Form submitted successfully!");
}}
>
{
if (value.selectedKey == null) {
return "Please select an animal";
}
if (value.selectedKey === "snake") {
return "Snakes are not allowed";
}
return true;
}}
>
Favorite Animal
{animals.map((animal) => (
{animal.name}
))}
Submit
);
}
```
### Read Only
Use the `isReadOnly` prop to make the ComboBox read-only. The selected value can be focused, but it cannot be changed.
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
const animals = [
{id: "aardvark", name: "Aardvark"},
{id: "cat", name: "Cat"},
{id: "dog", name: "Dog"},
{id: "kangaroo", name: "Kangaroo"},
{id: "panda", name: "Panda"},
{id: "snake", name: "Snake"},
];
export function ReadOnly() {
return (
Favorite Animal
{animals.map((animal) => (
{animal.name}
))}
);
}
```
### Multiple Selection
Set `selectionMode="multiple"` to allow selecting more than one option. In multiple selection mode, use `ComboBox.Value` to display the selected items and pass `selectionMode="multiple"` to the inner `ListBox` as well. Selection is controlled with the `value` / `defaultValue` (`Key[]`) props and the `onChange` handler.
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function MultipleSelection() {
return (
Favorite Animals
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {Button, ComboBox, FieldError, Form, Input, Label, ListBox, Surface} from "@heroui/react";
export function OnSurface() {
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert("Form submitted successfully!");
};
return (
Favorite Animal
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
Submit
);
}
```
## Customization
### Tailwind CSS
```tsx
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
const itemClass =
"rounded-lg data-[focused=true]:bg-muted/70 data-[selected=true]:font-medium data-[selected=true]:text-foreground";
export function CustomStyles() {
return (
Framework
React
Vue
Svelte
);
}
```
### Global CSS
To customize the ComboBox component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.combo-box {
@apply flex flex-col gap-1;
}
.combo-box__input-group {
@apply relative inline-flex items-center;
}
.combo-box__trigger {
@apply absolute right-0 text-muted;
}
.combo-box__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The ComboBox component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/combo-box.css)):
#### Base Classes \[!toc]
* `.combo-box` - Base ComboBox container
* `.combo-box__input-group` - Container for the input and trigger button
* `.combo-box__value` - The selected value display (used in multiple selection)
* `.combo-box__trigger` - The button that triggers the popover
* `.combo-box__popover` - The popover container
#### State Classes \[!toc]
* `.combo-box[data-invalid="true"]` - Invalid state
* `.combo-box[data-disabled="true"]` - Disabled ComboBox state
* `.combo-box__trigger[data-focus-visible="true"]` - Focused trigger state
* `.combo-box__trigger[data-disabled="true"]` - Disabled trigger state
* `.combo-box__trigger[data-open="true"]` - Open trigger 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 `[data-disabled="true"]` on ComboBox
* **Open**: `[data-open="true"]` on trigger
## API Reference
### ComboBox
| Prop | Type | Default | Description |
| ----------------------- | ---------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inputValue` | `string` | - | Current input value (controlled) |
| `defaultInputValue` | `string` | - | Default input value (uncontrolled) |
| `onInputChange` | `(value: string) => void` | - | Handler called when the input value changes |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | Whether single or multiple selection is enabled |
| `selectedKey` | `Key \| null` | - | Current selected key (controlled, single selection) |
| `defaultSelectedKey` | `Key \| null` | - | Default selected key (uncontrolled, single selection) |
| `onSelectionChange` | `(key: Key \| null) => void` | - | Handler called when the selection changes (single selection) |
| `value` | `Key \| null \| Key[]` | - | The currently selected keys (controlled). `Key[]` when `selectionMode="multiple"` |
| `defaultValue` | `Key \| null \| Key[]` | - | The initial selected keys (uncontrolled). `Key[]` when `selectionMode="multiple"` |
| `onChange` | `(value: Key \| null \| Key[]) => void` | - | Handler called when the selection changes |
| `items` | `Iterable` | - | The items to display in the listbox |
| `disabledKeys` | `Iterable` | - | Keys of disabled items |
| `defaultFilter` | `(text: string, inputValue: string) => boolean` | - | Custom filter function for filtering items |
| `isDisabled` | `boolean` | - | Whether the ComboBox is disabled |
| `isReadOnly` | `boolean` | - | Whether the input can be selected but not changed by the user |
| `isRequired` | `boolean` | - | Whether user input is required |
| `isInvalid` | `boolean` | - | Whether the ComboBox value is invalid |
| `validate` | `(value: ComboBoxValidationValue) => ValidationError \| true \| null \| undefined` | - | A function that returns an error message if a given value is invalid. Validation errors are displayed to the user when the form is submitted if `validationBehavior="native"`. For realtime validation, use the `isInvalid` prop instead |
| `validationBehavior` | `"native" \| "aria"` | `"native"` | Whether to use native HTML form validation to prevent form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA |
| `name` | `string` | - | The name of the input, used when submitting an HTML form |
| `form` | `string` | - | The id of a `` element to associate the input with |
| `formValue` | `"text" \| "key"` | `"key"` | Whether the text or key of the selected item is submitted as part of an HTML form. When `allowsCustomValue` is `true`, this option does not apply and the text is always submitted |
| `autoComplete` | `string` | - | Describes the type of autocomplete functionality |
| `autoFocus` | `boolean` | - | Whether the element should receive focus on render |
| `allowsCustomValue` | `boolean` | - | Whether the ComboBox allows custom values not in the list |
| `allowsEmptyCollection` | `boolean` | - | Whether the ComboBox allows an empty collection |
| `menuTrigger` | `"focus" \| "input" \| "manual"` | `"focus"` | The interaction required to display the ComboBox menu |
| `shouldFocusWrap` | `boolean` | - | Whether keyboard navigation is circular |
| `fullWidth` | `boolean` | `false` | Whether the ComboBox 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. |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | ComboBox content or render function |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### ComboBox.InputGroup
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | InputGroup content |
### ComboBox.Value
Renders the selected values of a ComboBox, or a placeholder if no value is selected. By default the selected items are rendered as a comma separated list. Use the render function to customize this (for example, to display tags).
| Prop | Type | Default | Description |
| ------------- | -------------------------------------------------------------- | ------- | ---------------------------------------------- |
| `placeholder` | `ReactNode` | - | A value to display when no items are selected |
| `className` | `string \| (values: ComboBoxValueRenderProps) => string` | - | Additional CSS classes |
| `children` | `ReactNode \| (values: ComboBoxValueRenderProps) => ReactNode` | - | Custom render function for the selected values |
### ComboBox.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom trigger content |
### ComboBox.Popover
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------------------------------------ |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | Placement of the popover relative to the trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Content children |
### Render Props
When using render functions with ComboBox, these values are provided:
| Prop | Type | Description |
| -------------- | --------------- | --------------------------- |
| `state` | `ComboBoxState` | The state of the ComboBox |
| `inputValue` | `string` | The current input value |
| `selectedKey` | `Key \| null` | The currently selected key |
| `selectedItem` | `Node \| null` | The currently selected item |
## Accessibility
The ComboBox component implements the ARIA comboBox pattern and provides:
* Full keyboard navigation support
* Screen reader announcements for selection changes and input changes
* Proper focus management
* Support for disabled states
* Typeahead search functionality
* HTML form integration
* Support for custom values
For more information, see the [React Aria ComboBox documentation](https://react-spectrum.adobe.com/react-aria/ComboBox.html).
## Related Components
## Related Components
* **Listbox**: Scrollable list of selectable items
* **Popover**: Displays content in context with a trigger
* **Input**: Single-line text input built on React Aria
# Select
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/select
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(pickers)/select.mdx
> A select displays a collapsible list of options and allows a user to select one of them
## Usage
```tsx
import { Select } from "@heroui/react";
```
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function Default() {
return (
State
Florida
Delaware
California
Texas
New York
Washington
);
}
```
## Anatomy
```tsx
import {Select, Label, Description, Header, ListBox, Separator} from "@heroui/react";
export default () => (
);
```
## Examples
### Variants
The Select 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, ListBox, Select} from "@heroui/react";
export function Variants() {
return (
Primary variant
Option 1
Option 2
Secondary variant
Option 1
Option 2
);
}
```
### Full Width
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function FullWidth() {
return (
Favorite Animal
Cat
Dog
Bird
);
}
```
### With Description
```tsx
import {Description, Label, ListBox, Select} from "@heroui/react";
export function WithDescription() {
return (
State
Florida
Delaware
California
Texas
New York
Washington
Select your state of residence
);
}
```
### Required
```tsx
"use client";
import {Button, FieldError, Form, Label, ListBox, Select} from "@heroui/react";
export function Required() {
const onSubmit = (e: React.SyntheticEvent) => {
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 (
State
Florida
Delaware
California
Texas
New York
Washington
Country
United States
Canada
Mexico
United Kingdom
France
Germany
Submit
);
}
```
### Disabled
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function Disabled() {
return (
State
Florida
Delaware
California
Texas
New York
Washington
Countries to Visit
Argentina
Venezuela
Japan
France
Italy
Spain
);
}
```
### With Disabled Options
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function WithDisabledOptions() {
return (
Animal
Dog
Cat
Bird
Kangaroo
Elephant
Tiger
);
}
```
### Multiple Select
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function MultipleSelect() {
return (
Countries to Visit
Argentina
Venezuela
Japan
France
Italy
Spain
Thailand
New Zealand
Iceland
);
}
```
### With Sections
```tsx
import {Header, Label, ListBox, Select, Separator} from "@heroui/react";
export function WithSections() {
return (
Country
United States
Canada
Mexico
United Kingdom
France
Germany
Spain
Italy
Japan
China
India
South Korea
);
}
```
### Controlled
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const states = [
{
id: "california",
name: "California",
},
{
id: "texas",
name: "Texas",
},
{
id: "florida",
name: "Florida",
},
{
id: "new-york",
name: "New York",
},
{
id: "illinois",
name: "Illinois",
},
{
id: "pennsylvania",
name: "Pennsylvania",
},
];
const [state, setState] = useState("california");
const selectedState = states.find((s) => s.id === state);
return (
setState(value)}
>
State (controlled)
{states.map((state) => (
{state.name}
))}
Selected: {selectedState?.name || "None"}
);
}
```
### Controlled Multiple
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Label, ListBox, Select} from "@heroui/react";
import React from "react";
export function ControlledMultiple() {
const [selected, setSelected] = React.useState(["california", "texas"]);
return (
setSelected(keys as Key[])}
>
States (controlled multiple)
California
Texas
Florida
New York
Illinois
Pennsylvania
Selected: {selected.length > 0 ? selected.join(", ") : "None"}
);
}
```
### Controlled Open State
```tsx
"use client";
import {Button, Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
export function ControlledOpenState() {
const [isOpen, setIsOpen] = useState(false);
return (
State
Florida
Delaware
California
Texas
New York
Washington
setIsOpen(!isOpen)}>{isOpen ? "Close" : "Open"} Select
Select is {isOpen ? "open" : "closed"}
);
}
```
### Asynchronous Loading
```tsx
"use client";
import {Label, ListBox, Select, Spinner} from "@heroui/react";
import {useAsyncList} from "@react-stately/data";
import {Collection, ListBoxLoadMoreItem} from "react-aria-components";
interface Pokemon {
name: string;
}
export function AsynchronousLoading() {
const list = useAsyncList({
async load({cursor, signal}) {
const res = await fetch(cursor || `https://pokeapi.co/api/v2/pokemon`, {signal});
const json = await res.json();
return {
cursor: json.next,
items: json.results,
};
},
});
return (
Pick a Pokemon
{(item: Pokemon) => (
{item.name}
)}
Loading more...
);
}
```
### Custom Indicator
```tsx
import {ChevronsExpandVertical} from "@gravity-ui/icons";
import {Label, ListBox, Select} from "@heroui/react";
export function CustomIndicator() {
return (
State
Florida
Delaware
California
Texas
New York
Washington
);
}
```
### With Clear Button
`Select.Trigger` is a button, so a nested `` cannot be used for clear. Compose `Select.ClearButton` inside the trigger instead — it renders as a non-button control and does not open the menu.
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function WithClearButton() {
return (
State
Florida
Delaware
California
Texas
New York
Washington
);
}
```
Because ARIA treats the children of a button as presentational, the clear control cannot take focus. It is therefore `aria-hidden` and acts as a pointer affordance only. When a `Select.ClearButton` is composed, the trigger also clears on `Backspace` or `Delete`, which is how keyboard and screen reader users clear the selection. Both paths call `onClear`.
### Custom Value
```tsx
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
Description,
Label,
ListBox,
Select,
} from "@heroui/react";
export function CustomValue() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
return (
User
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItems = state.selectedItems;
if (selectedItems.length > 1) {
return `${selectedItems.length} users selected`;
}
const selectedItem = users.find((user) => user.id === selectedItems[0]?.key);
if (!selectedItem) {
return defaultChildren;
}
return (
{selectedItem.fallback}
{selectedItem.name}
);
}}
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### Render Function
```tsx
"use client";
import {Label, ListBox, Select} from "@heroui/react";
export function RenderFunction() {
return (
}
>
State
Florida
Delaware
California
Texas
New York
Washington
);
}
```
### In Surface
When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
"use client";
import {Button, FieldError, Form, Label, ListBox, Select, Surface} from "@heroui/react";
export function OnSurface() {
const onSubmit = (e: React.SyntheticEvent) => {
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 (
State
Florida
Delaware
California
Texas
New York
Washington
Country
United States
Canada
Mexico
United Kingdom
France
Germany
Submit
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Label, ListBox, Select} from "@heroui/react";
const itemClass =
"rounded-lg data-[focused=true]:bg-accent/10 data-[selected=true]:text-foreground";
export function CustomStyles() {
return (
Plan
Free
Pro
);
}
```
### Global CSS
To customize the Select component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.select {
@apply flex flex-col gap-1;
}
.select__trigger {
@apply rounded-lg border border-border bg-surface p-2;
}
.select__value {
@apply text-current;
}
.select__indicator {
@apply text-muted;
}
.select__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Select component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/select.css)):
#### Base Classes \[!toc]
* `.select` - Base select container
* `.select__trigger` - The button that triggers the select
* `.select__value` - The displayed value or placeholder
* `.select__clear-button` - The optional clear control inside the trigger
* `.select__indicator` - The dropdown indicator icon
* `.select__popover` - The popover container
#### Variant Classes \[!toc]
* `.select--primary` - Primary variant with shadow (default)
* `.select--secondary` - Secondary variant without shadow, suitable for use in surfaces
#### State Classes \[!toc]
* `.select[data-invalid="true"]` - Invalid state
* `.select__trigger[data-focus-visible="true"]` - Focused trigger state
* `.select__trigger[data-disabled="true"]` - Disabled trigger state
* `.select__value[data-placeholder="true"]` - Placeholder state
* `.select__clear-button[data-empty="true"]` - Clear button hidden when no selection
* `.select__indicator[data-open="true"]` - Open indicator 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 `[data-disabled="true"]` on select
* **Open**: `[data-open="true"]` on indicator
## API Reference
### Select
| Prop | Type | Default | Description |
| --------------- | ------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `placeholder` | `string` | `'Select an item'` | Temporary text that occupies the select when it is empty |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | Whether single or multiple selection is enabled |
| `isOpen` | `boolean` | - | Sets the open state of the menu (controlled) |
| `defaultOpen` | `boolean` | - | Sets the default open state of the menu (uncontrolled) |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Handler called when the open state changes |
| `disabledKeys` | `Iterable` | - | Keys of disabled items |
| `isDisabled` | `boolean` | - | Whether the select is disabled |
| `value` | `Key \| Key[] \| null` | - | Current value (controlled) |
| `defaultValue` | `Key \| Key[] \| null` | - | Default value (uncontrolled) |
| `onChange` | `(value: Key \| Key[] \| null) => void` | - | Handler called when the value changes |
| `onClear` | `() => void` | - | Handler called when the selection is cleared |
| `isRequired` | `boolean` | - | Whether user input is required |
| `isInvalid` | `boolean` | - | Whether the select value is invalid |
| `name` | `string` | - | The name of the input, used when submitting an HTML form |
| `autoComplete` | `string` | - | Describes the type of autocomplete functionality |
| `fullWidth` | `boolean` | `false` | Whether the select 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. |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Select content or render function |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Select.Trigger
| Prop | Type | Default | Description |
| ----------- | ----------------------------- | ------- | ---------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Trigger content or render function |
### Select.Value
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode \| RenderFunction` | - | Value content or render function |
| `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. |
### Select.Indicator
| Prop | Type | Default | Description |
| ----------- | ----------- | ------- | ------------------------ |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom indicator content |
### Select.ClearButton
| Prop | Type | Default | Description |
| ----------- | ------------------------- | ------- | ------------------------------------------ |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Custom content, replacing the default icon |
| `onClick` | `(e: MouseEvent) => void` | - | Handler called when the control is clicked |
`Select.ClearButton` renders as a `span` so it can live inside `Select.Trigger` (a button) without nesting buttons. It is visually hidden when the selection is empty.
### Select.Popover
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------------------------------------ |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | Placement of the popover relative to the trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `ReactNode` | - | Content children |
### Render Props
When using render functions with Select.Value, these values are provided:
| Prop | Type | Description |
| ----------------- | ------------- | ---------------------------------- |
| `defaultChildren` | `ReactNode` | The default rendered value |
| `isPlaceholder` | `boolean` | Whether the value is a placeholder |
| `state` | `SelectState` | The state of the select |
| `selectedItems` | `Node[]` | The currently selected items |
## Accessibility
The Select component implements the ARIA listbox pattern and provides:
* Full keyboard navigation support
* Screen reader announcements for selection changes
* Proper focus management
* Support for disabled states
* Typeahead search functionality
* HTML form integration
For more information, see the [React Aria Select documentation](https://react-spectrum.adobe.com/react-aria/Select.html).
## Related Components
## Related Components
* **Listbox**: Scrollable list of selectable items
* **Popover**: Displays content in context with a trigger
* **Label**: Accessible label for form controls
# Kbd
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/kbd
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(typography)/kbd.mdx
> Display keyboard shortcuts and key combinations
## Usage
```tsx
import { Kbd } from "@heroui/react";
```
```tsx
import {Kbd} from "@heroui/react";
export function Basic() {
return (
K
P
C
D
);
}
```
## Anatomy
```tsx
import { Kbd } from "@heroui/react";
export default () => (
⌘
K
);
```
## Examples
### Variants
```tsx
import {Kbd} from "@heroui/react";
export function Variants() {
return (
Copy:
C
C
Paste:
V
V
Cut:
X
X
Undo:
Z
Z
Redo:
Z
Z
);
}
```
### Navigation Keys
```tsx
import {Kbd} from "@heroui/react";
export function NavigationKeys() {
return (
);
}
```
### Inline Usage
```tsx
import {Kbd} from "@heroui/react";
export function InlineUsage() {
return (
Press{" "}
Esc
{" "}
to close the dialog.
Use{" "}
K
{" "}
to open the command palette.
Navigate with{" "}
{" "}
and{" "}
{" "}
arrow keys.
Save your work with{" "}
S
{" "}
regularly.
);
}
```
### Instructional Text
```tsx
import {Kbd} from "@heroui/react";
export function InstructionalText() {
return (
Quick Actions
• Open search:{" "}
K
• Toggle sidebar:{" "}
B
• New file:{" "}
N
• Quick save:{" "}
S
);
}
```
### Special Keys
```tsx
import {Kbd} from "@heroui/react";
export function SpecialKeys() {
return (
Press{" "}
{" "}
to confirm or{" "}
{" "}
to cancel.
Use{" "}
{" "}
to navigate between form fields and{" "}
{" "}
to go back.
Hold{" "}
{" "}
to temporarily enable panning mode.
);
}
```
## Customization
### Tailwind CSS
```tsx
import {Kbd} from "@heroui/react";
export function CustomStyles() {
return (
K
P
);
}
```
### Global CSS
To customize the Kbd component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.kbd {
@apply bg-gray-100 dark:bg-gray-800 border-gray-300;
}
.kbd__abbr {
@apply font-bold;
}
.kbd__content {
@apply text-sm;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Kbd component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/kbd.css)):
#### Base Classes \[!toc]
* `.kbd` - Base keyboard key styles with background, border, and spacing
* `.kbd__abbr` - Abbreviation element for modifier keys
* `.kbd__content` - Content wrapper for key text
## API Reference
### Kbd
| Prop | Type | Default | Description | |
| ----------- | ----------------- | --------- | ------------------ | --------------------------- |
| `children` | `React.ReactNode` | - | Content of the key | |
| `variant` | \`"default" | "light"\` | `default` | Variant of the keyboard key |
| `className` | `string` | - | Custom CSS classes | |
### Kbd.Abbr
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | --------------------------------------------------------- |
| `title` | `string` | - | Title attribute for accessibility (e.g., "Command" for ⌘) |
| `children` | `React.ReactNode` | - | The symbol or text to display (e.g., ⌘, ⌥, ⇧) |
| `className` | `string` | - | Custom CSS classes |
### Kbd.Key
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ----------------------- |
| `children` | `React.ReactNode` | - | Text content of the key |
| `className` | `string` | - | Custom CSS classes |
### Kbd.Content Type
Available key values for the `keyValue` prop:
| Modifier Keys | Special Keys | Navigation Keys | Function Keys |
| ------------- | ------------ | --------------- | ------------- |
| `command` | `enter` | `up` | `fn` |
| `shift` | `delete` | `down` | |
| `ctrl` | `escape` | `left` | |
| `option` | `tab` | `right` | |
| `alt` | `space` | `pageup` | |
| `win` | `capslock` | `pagedown` | |
| | `help` | `home` | |
| | | `end` | |
## Related Components
# Typography
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/typography
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(typography)/typography.mdx
> A semantic typography primitive for headings, body copy, and inline code built on React Aria Components Text.
## Usage
```tsx
import {Typography} from "@heroui/react";
```
```tsx
import {Typography} from "@heroui/react";
export const Default = () => {
return (
Build better interfaces
Typography that stays semantic
Composable by default
Small heading
HeroUI Typography uses React Aria Components Text as the primitive, with semantic typography
types and render-prop polymorphism.
Smaller muted body copy for secondary descriptions.
pnpm add @heroui/react
);
};
```
## Anatomy
`Typography` maps visual `type` values to semantic elements by default.
```tsx
import {Typography} from "@heroui/react";
export default () => (
Heading
);
```
## Examples
### Scale
```tsx
import {Typography} from "@heroui/react";
const scale = [
{
label: "h1",
meta: "36px / 600 / 1.11 / tight",
sample: "Build better interfaces",
type: "h1" as const,
},
{
label: "h2",
meta: "30px / 600 / 1.17 / tight",
sample: "Built for the intelligence age",
type: "h2" as const,
},
{
label: "h3",
meta: "24px / 600 / 1.25 / tight",
sample: "Pricing on your terms",
type: "h3" as const,
},
{
label: "h4",
meta: "20px / 600 / 1.33 / tight",
sample: "Apply to the startup program",
type: "h4" as const,
},
{
label: "h5",
meta: "18px / 600 / 1.39 / tight",
sample: "Card titles",
type: "h5" as const,
},
{
label: "h6",
meta: "16px / 600 / 1.50 / tight",
sample: "Smaller feature headers",
type: "h6" as const,
},
{
label: "body",
meta: "16px / 400 / 1.75",
sample: "Primary body text used across documentation, marketing copy, and descriptions.",
type: "body" as const,
},
{
label: "body-sm",
meta: "14px / 400 / 1.50",
sample: "Secondary body, table cells, navigation, and sidebar items.",
type: "body-sm" as const,
},
{
label: "body-xs",
meta: "12px / 400 / 1.25",
sample: "Captions, badges, helper text, and fine print.",
type: "body-xs" as const,
},
{
label: "code",
meta: "14px / mono",
sample: "pnpm add @heroui/react",
type: "code" as const,
},
] as const;
export const TypographyScale = () => {
return (
{scale.map((row) => (
{row.label}
{row.meta}
{row.sample}
))}
);
};
```
### Primitives
```tsx
import {Typography} from "@heroui/react";
export const Primitives = () => {
return (
Dashboard
Convenience primitives are thin wrappers over Typography, so you can choose explicit
composition without learning a second styling system.
Paragraph supports base, sm, and xs sizes.
Typography.Code
);
};
```
* `Typography.Heading` maps `level={1..6}` to `type="h1"` through `type="h6"`.
* `Typography.Paragraph` maps `size="base" | "sm" | "xs"` to body text styles.
* `Typography.Code` maps to the inline code style.
* `Typography.Prose` styles rich content passed as regular HTML children.
### Prose
```tsx
import {Typography} from "@heroui/react";
export const Prose = () => {
return (
Prose title
Prose is for authored content where the markup is already semantic and HeroUI applies the
default typography rhythm.
Section title
Inline code like render receives the same code treatment as the Typography
primitive.
);
};
```
### Render Props
```tsx
"use client";
import {Typography} from "@heroui/react";
export const RenderProps = () => {
return (
{children} } type="h1">
H1 visual style, h2 semantic element
{children} }>
The render prop can swap the underlying element while preserving HeroUI props and styles.
);
};
```
Use the React Aria Components-style `render` prop when you need to customize the rendered element.
## Customization
### Tailwind CSS
```tsx
import {Typography} from "@heroui/react";
export function CustomStyles() {
return (
Changelog
Faster search results
Queries now return in under 200ms thanks to an improved index.
);
}
```
### Global CSS
To customize the Typography component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.typography--h1 {
@apply font-extrabold tracking-tight;
}
.typography--code {
@apply rounded-md bg-default-100 font-mono dark:bg-default-50/20;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The Typography component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/typography.css)):
#### Base Classes \[!toc]
* `.typography` - Base typography primitive
* `.typography-prose` - Rich prose container
#### Type Classes \[!toc]
* `.typography--h1` through `.typography--h6`
* `.typography--body`, `.typography--body-sm`, `.typography--body-xs`
* `.typography--code`
#### Modifier Classes \[!toc]
* `.typography--align-start`, `.typography--align-center`, `.typography--align-end`, `.typography--align-justify`
* `.typography--color-default`, `.typography--color-muted`
* `.typography--truncate`
* `.typography--weight-normal`, `.typography--weight-medium`, `.typography--weight-semibold`, `.typography--weight-bold`
## API Reference
### Typography
| Prop | Type | Default | Description |
| ---------- | -------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------- |
| `type` | `'h1' \| 'h2' \| 'h3' \| 'h4' \| 'h5' \| 'h6' \| 'body' \| 'body-sm' \| 'body-xs' \| 'code'` | `'body'` | Semantic typography style. |
| `align` | `'start' \| 'center' \| 'end' \| 'justify'` | `'start'` | Text alignment. |
| `color` | `'default' \| 'muted'` | `'default'` | Text color. |
| `weight` | `'normal' \| 'medium' \| 'semibold' \| 'bold'` | - | Font weight override. |
| `truncate` | `boolean` | - | Truncates the text to one line with ellipsis. |
| `render` | `DOMRenderFunction` | - | Custom render function from React Aria. |
| `children` | `ReactNode` | - | Text content. |
## Related Components
# ScrollShadow
**Category**: react
**URL**: https://heroui.com/en/docs/react/components/scroll-shadow
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(utilities)/scroll-shadow.mdx
> Apply visual shadows to indicate scrollable content overflow with automatic detection of scroll position.
## Usage
```tsx
import { ScrollShadow } from "@heroui/react";
```
```tsx
import {ScrollShadow} from "@heroui/react";
export default function Default() {
return (
{Array.from({length: 10}).map((_, idx) => (
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor quam.
Morbi accumsan cursus enim, sed ultricies sapien.
))}
);
}
```
## Examples
### Orientation
```tsx
import {Card, ScrollShadow} from "@heroui/react";
const images = [
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/robot1.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/avocado.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/oranges.jpeg",
];
export default function Orientation() {
const getRandomImage = (idx: number) => {
return images[idx % images.length];
};
return (
Vertical
{Array.from({length: 10}).map((_, idx) => (
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor
quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
Horizontal
{Array.from({length: 10}).map((_, idx) => (
Bridging the Future
Today, 6:30 PM
))}
);
}
```
### Shadow Size
```tsx
import {ScrollShadow} from "@heroui/react";
export default function CustomSize() {
return (
{Array.from({length: 10}).map((_, idx) => (
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor quam.
Morbi accumsan cursus enim, sed ultricies sapien.
))}
);
}
```
### With Card
```tsx
import {Button, Card, ScrollShadow} from "@heroui/react";
export default function WithCard() {
return (
Terms and Conditions
Please review before proceeding
{Array.from({length: 10}).map((_, idx) => (
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor
quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
Cancel
Accept
);
}
```
### Hide Scroll Bar
```tsx
import {ScrollShadow} from "@heroui/react";
export default function HideScrollBar() {
return (
{Array.from({length: 10}).map((_, idx) => (
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor quam.
Morbi accumsan cursus enim, sed ultricies sapien.
))}
);
}
```
### Visibility Change
```tsx
"use client";
import type {ScrollShadowVisibility} from "@heroui/react";
import {Card, ScrollShadow} from "@heroui/react";
import {useState} from "react";
const images = [
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/robot1.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/avocado.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/oranges.jpeg",
];
export default function VisibilityChange() {
const [verticalState, setVerticalState] = useState("none");
const [horizontalState, setHorizontalState] = useState("none");
const getRandomImage = (idx: number) => {
return images[idx % images.length];
};
return (
Vertical Shadow State: {verticalState}
setVerticalState(visibility)}
>
{Array.from({length: 10}).map((_, idx) => (
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar risus non
risus hendrerit venenatis. Pellentesque sit amet hendrerit risus, sed porttitor
quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
Horizontal Shadow State: {horizontalState}
setHorizontalState(visibility)}
>
{Array.from({length: 10}).map((_, idx) => (
Bridging the Future
Today, 6:30 PM
))}
);
}
```
## Customization
### Tailwind CSS
```tsx
import {ScrollShadow} from "@heroui/react";
const entries = [
"Reviewed quarterly goals with the design team.",
"Shipped dark mode tokens to production.",
"Merged accessibility fixes for form fields.",
"Published updated component documentation.",
"Scheduled performance audit for next sprint.",
"Added scroll shadow demos to the docs site.",
];
export function CustomStyles() {
return (
{entries.map((entry) => (
{entry}
))}
);
}
```
### Global CSS
To customize the ScrollShadow component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.scroll-shadow {
@apply rounded-xl border border-default-200;
}
.scroll-shadow--vertical {
@apply pr-2; /* Add padding for custom scrollbar styling */
}
.scroll-shadow--horizontal {
@apply pb-2;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The ScrollShadow component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/scroll-shadow.css)):
#### Base Classes \[!toc]
* `.scroll-shadow` - Root container element
#### Orientation Variants \[!toc]
* `.scroll-shadow--vertical` - Vertical scrolling (default)
* `.scroll-shadow--horizontal` - Horizontal scrolling
#### State Modifiers \[!toc]
* `.scroll-shadow--hide-scrollbar` - Hides native scrollbar
### CSS Variables
The ScrollShadow component uses CSS variables to size the fade mask and reserve space for visible native scrollbars:
| Variable | Default | Description |
| -------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `--scroll-shadow-size` | `40px` | Controls the fade gradient size. This is set from the `size` prop. |
| `--scroll-shadow-offset` | `0px` | How far the container must be scrolled before the fade starts. This is set from the `offset` prop. |
| `--scroll-shadow-scrollbar-size` | `10px` (`0px` when `hideScrollBar`) | Reserves a solid mask gutter for the native scrollbar so the fade does not cover it. Override for wider scrollbars. |
### Data Attributes
The component uses data attributes to control shadow visibility:
* **Scroll States**: `[data-top-scroll]`, `[data-bottom-scroll]`, `[data-left-scroll]`, `[data-right-scroll]` - Applied when content can be scrolled in that direction
* **Combined States**: `[data-top-bottom-scroll]`, `[data-left-right-scroll]` - Applied when content can be scrolled in both directions
* **Orientation**: `[data-orientation="vertical"]` or `[data-orientation="horizontal"]` - Indicates scroll direction
* **Size**: `[data-scroll-shadow-size]` - Contains the shadow gradient size value
* **Shadow Mode**: `[data-scroll-shadow-mode]` - `"auto"` when the fade is derived from the scroll position, `"manual"` when `visibility` is controlled or `isEnabled` is `false`
### Scroll-Driven Fade
In `auto` mode, browsers that support [scroll-driven animations](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline)
derive the fade from the scroll position in CSS. The mask is therefore correct on the very
first paint, with no measurement and no flash of unfaded content during hydration. Browsers
without support fall back to the `[data-*-scroll]` attributes above, which are written after
hydration. Two things to keep in mind when customizing:
* In `auto` mode the root always resolves a `mask-image`, even when there is nothing to scroll.
That makes it a stacking context and a containing block for `position: fixed` descendants.
Set `visibility` explicitly if you need to opt out.
* The scroll-driven fade uses the `animation` property on the root. Applying an `animate-*`
utility to the same element replaces it and leaves no fade. Animate a wrapper instead.
## API Reference
### ScrollShadow
| Prop | Type | Default | Description |
| -------------------- | ---------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------- |
| `orientation` | `"vertical"` \| `"horizontal"` | `"vertical"` | The scroll direction |
| `variant` | `"fade"` | `"fade"` | The visual shadow effect style |
| `size` | `number` | `40` | The shadow gradient size in pixels |
| `offset` | `number` | `0` | The scroll offset before showing shadows (in pixels) |
| `hideScrollBar` | `boolean` | `false` | Whether to hide the native scrollbar |
| `isEnabled` | `boolean` | `true` | Whether scroll shadow detection is enabled |
| `visibility` | `"auto"` \| `"both"` \| `"top"` \| `"bottom"` \| `"left"` \| `"right"` \| `"none"` | `"auto"` | Controlled shadow visibility state |
| `onVisibilityChange` | `(visibility: ScrollShadowVisibility) => void` | - | Callback invoked when shadow visibility changes |
| `className` | `string` | - | Additional CSS classes to apply to the root element |
| `children` | `ReactNode` | - | The scrollable content |
## Related Components
# ButtonGroup 按钮组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/button-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(buttons)/button-group.mdx
> 将相关按钮组合在一起,保持一致的样式与间距
## 用法
```tsx
import { ButtonGroup, Button } from '@heroui/react';
```
```tsx
import {
ChevronDown,
ChevronLeft,
ChevronRight,
CodeFork,
Ellipsis,
Picture,
Pin,
QrCode,
Star,
TextAlignCenter,
TextAlignJustify,
TextAlignLeft,
TextAlignRight,
ThumbsDown,
ThumbsUp,
Video,
} from "@gravity-ui/icons";
import {Button, ButtonGroup, Chip, Description, Dropdown, Label} from "@heroui/react";
export function Basic() {
return (
{/* 单个按钮与下拉菜单 */}
合并拉取请求
创建合并提交
此分支上的所有提交都将加入基础分支
压缩并合并
此分支上的 14 个提交将合并为一次提交并加入基础分支
变基并合并
此分支上的 14 个提交将变基后加入基础分支
{/* 独立按钮 */}
复刻
24
扫码支付
2.4K
星标
104
已置顶
{/* 上一页 / 下一页 */}
上一页
下一页
{/* 内容类型选择 */}
{/* 文本对齐 */}
左对齐
居中
右对齐
{/* 仅图标:对齐 */}
);
}
```
## 组件结构
```tsx
import { ButtonGroup, Button } from '@heroui/react';
export default () => (
First
Second
Third
);
```
> **ButtonGroup** 将多个 Button 组合在一起,应用一致的样式、间距与自动圆角处理。它通过 React Context 向所有子按钮传递 `size`、`variant` 与 `isDisabled` 属性。
## 示例
### 变体
```tsx
import {Button, ButtonGroup} from "@heroui/react";
export function Variants() {
return (
);
}
```
### 尺寸
```tsx
import {Button, ButtonGroup} from "@heroui/react";
export function Sizes() {
return (
);
}
```
### 方向
使用 `orientation` 属性可水平或垂直排列按钮。
```tsx
import {TextAlignCenter, TextAlignJustify, TextAlignLeft, TextAlignRight} from "@gravity-ui/icons";
import {Button, ButtonGroup} from "@heroui/react";
export function Orientation() {
return (
);
}
```
### 带图标
```tsx
import {Globe, Plus, TrashBin} from "@gravity-ui/icons";
import {Button, ButtonGroup} from "@heroui/react";
export function WithIcons() {
return (
);
}
```
### 宽度充满
```tsx
import {TextAlignCenter, TextAlignLeft, TextAlignRight} from "@gravity-ui/icons";
import {Button, ButtonGroup} from "@heroui/react";
export function FullWidth() {
return (
第一项
第二项
第三项
);
}
```
### 禁用状态
```tsx
import {Button, ButtonGroup} from "@heroui/react";
export function Disabled() {
return (
组已禁用,但单个按钮可覆盖
第一项
第二项
第三项(可用)
);
}
```
### 无分隔线
省略按钮中的 ` ` 组件即可。
```tsx
import {Button, ButtonGroup} from "@heroui/react";
export function WithoutSeparator() {
return (
第一项
第二项
第三项
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ChevronDown} from "@gravity-ui/icons";
import {Button, ButtonGroup, Description, Dropdown, Label} from "@heroui/react";
export function CustomStyles() {
return (
合并拉取请求
创建合并提交
此分支的所有提交将添加到基础分支
压缩并合并
此分支的 14 个提交将在基础分支中合并为一个提交
变基并合并
此分支的 14 个提交将变基后添加到基础分支
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ButtonGroup 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.button-group {
@apply gap-2 rounded-lg;
}
.button-group__separator {
@apply opacity-25;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ButtonGroup 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/button-group.css)):
#### 基础类 \[!toc]
* `.button-group` - 按钮组根容器
* `.button-group--full-width` - 全宽修饰符
* `.button-group__separator` - 按钮之间的分隔元素
ButtonGroup 组件自动为按钮应用圆角:
* 第一个按钮圆角左/起始边
* 最后一个按钮圆角右/结束边
* 中间按钮无圆角
* 单个按钮四边全圆角
在每个 Button 内(第一个除外)添加 ` ` 以显示分隔线。
## API 参考
### ButtonGroup
| Prop | 类型 | 默认值 | 描述 |
| ------------- | --------------------------------------------------------------- | -------------- | ------------------- |
| `variant` | `'primary' \| 'secondary' \| 'tertiary' \| 'ghost' \| 'danger'` | - | 应用于组内所有按钮的视觉变体 |
| `size` | `'sm' \| 'md' \| 'lg'` | - | 应用于组内所有按钮的尺寸 |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | 按钮组方向 |
| `fullWidth` | `boolean` | `false` | 是否占满容器宽度 |
| `isDisabled` | `boolean` | `false` | 是否禁用组内所有按钮(单个按钮可覆盖) |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 要组合的 Button 组件 |
### ButtonGroup.Separator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
## 说明
* ButtonGroup 通过 React Context 向所有子 Button 传递 `size`、`variant` 与 `isDisabled` 属性
* **仅直接子按钮接收 ButtonGroup 属性** - 嵌套在其他组件(如 Modal、Dropdown 等)内的 Button 即使位于 ButtonGroup 后代也不会继承组属性
* 单个 Button 可通过 `isDisabled={false}` 覆盖组的 `isDisabled` 属性
* 组件自动处理按钮之间的圆角
* 在每个 Button 内(第一个除外)添加 ` ` 以显示分隔线
* 组内按钮移除 active/pressed 缩放变换,外观更统一
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Dropdown**: Context menu with actions and options
* **Chip**: Compact elements for tags and filters
# Button 按钮
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/button
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(buttons)/button.mdx
> 可点击的按钮组件,支持多种变体与状态
## 用法
```tsx
import { Button } from '@heroui/react';
```
```tsx
"use client";
import {Button} from "@heroui/react";
export function Basic() {
return console.log("按钮已按下")}>点我 ;
}
```
## 示例
### 变体
```tsx
import {Button} from "@heroui/react";
export function Variants() {
return (
主要
次要
第三
线框
幽灵
危险
柔和危险
);
}
```
### 尺寸
```tsx
import {Button} from "@heroui/react";
export function Sizes() {
return (
小
中
大
);
}
```
### 带图标
```tsx
import {Envelope, Globe, Plus, TrashBin} from "@gravity-ui/icons";
import {Button} from "@heroui/react";
export function WithIcons() {
return (
);
}
```
### 仅图标
```tsx
import {Ellipsis, Gear, TrashBin} from "@gravity-ui/icons";
import {Button} from "@heroui/react";
export function IconOnly() {
return (
);
}
```
### 加载中
```tsx
"use client";
import {Button, Spinner} from "@heroui/react";
import React from "react";
export function Loading() {
return (
{({isPending}) => (
<>
{isPending ? : null}
上传中…
>
)}
);
}
```
### 加载中(点击触发)
```tsx
"use client";
import {Paperclip} from "@gravity-ui/icons";
import {Button, Spinner} from "@heroui/react";
import React, {useState} from "react";
export function LoadingState() {
const [isLoading, setLoading] = useState(false);
const handlePress = () => {
setLoading(true);
setTimeout(() => setLoading(false), 2000);
};
return (
{({isPending}) => (
<>
{isPending ? : }
{isPending ? "上传中…" : "上传文件"}
>
)}
);
}
```
### 宽度充满
```tsx
import {Plus} from "@gravity-ui/icons";
import {Button} from "@heroui/react";
export function FullWidth() {
return (
);
}
```
### 禁用状态
```tsx
import {Button} from "@heroui/react";
export function Disabled() {
return (
主要
次要
第三
线框
幽灵
危险
);
}
```
### 社交媒体按钮
```tsx
import {Button} from "@heroui/react";
import {Icon} from "@iconify/react";
export function Social() {
return (
使用 Google 登录
使用 GitHub 登录
使用 Apple 登录
);
}
```
### 渲染函数
```tsx
"use client";
import {Button} from "@heroui/react";
export function RenderFunction() {
return (
(
)}
>
点按
);
}
```
### 添加自定义变体
可通过包装组件并添加自定义变体来扩展 HeroUI 组件。
```tsx
import type {ButtonProps} from "@heroui/react";
import type {VariantProps} from "tailwind-variants";
import {Button, buttonVariants} from "@heroui/react";
import {tv} from "tailwind-variants";
const myButtonVariants = tv({
base: "text-md font-semibold shadow-md text-shadow-lg data-[pending=true]:opacity-40",
defaultVariants: {
radius: "full",
variant: "primary",
},
extend: buttonVariants,
variants: {
radius: {
full: "rounded-full",
lg: "rounded-lg",
md: "rounded-md",
sm: "rounded-sm",
},
size: {
lg: "h-12 px-8",
md: "h-11 px-6",
sm: "h-10 px-4",
xl: "h-13 px-10",
},
variant: {
primary: "text-white dark:bg-white/10 dark:text-white dark:hover:bg-white/15",
},
},
});
type MyButtonVariants = VariantProps;
export type MyButtonProps = Omit &
MyButtonVariants & {className?: string};
function CustomButton({className, radius, variant, ...props}: MyButtonProps) {
return ;
}
export function CustomVariants() {
return 自定义按钮 ;
}
```
### 添加涟漪效果
Button 组件通过组合支持 ripple 效果,可将 ripple 组件作为子节点嵌套。本示例使用 [m3-ripple](https://github.com/saltyaom/m3-ripple)。
```tsx
"use client";
import {Button} from "@heroui/react";
import {Ripple} from "m3-ripple";
import "m3-ripple/ripple.css";
export function RippleEffect() {
return (
点我
);
}
```
## 自定义样式
### Tailwind CSS
````tsx
"use client";
import {Button} from "@heroui/react";
/**
* 下面的 `gradient-border` 类依赖全局 utility。
* 使用前请将其添加到全局 CSS(例如 `src/app/globals.css`):
*
* ```css
* @utility gradient-border {
* &::before {
* content: "";
* position: absolute;
* inset: 0;
* z-index: 0;
* border-radius: inherit;
* padding: var(--gradient-border-width, 1px);
* background: var(--gradient-border);
* pointer-events: none;
* -webkit-mask:
* linear-gradient(#fff 0 0) content-box,
* linear-gradient(#fff 0 0);
* -webkit-mask-composite: xor;
* mask:
* linear-gradient(#fff 0 0) content-box,
* linear-gradient(#fff 0 0);
* mask-composite: exclude;
* }
* }
* ```
*
* 在组件上通过 Tailwind 任意属性或内联样式设置
* `--gradient-border`(渐变)以及可选的 `--gradient-border-width`(默认 `1px`)。
*/
export function CustomStyles() {
return (
升级
);
}
````
### 全局 CSS
可使用 `@layer components` 指令自定义 Button 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.button {
@apply bg-purple-500 text-white hover:bg-purple-600;
}
.button--icon-only {
@apply rounded-lg bg-blue-500;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Button 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/button.css)):
#### 基础与尺寸类 \[!toc]
* `.button` - 基础按钮样式
* `.button--sm` - 小尺寸变体
* `.button--md` - 中尺寸变体
* `.button--lg` - 大尺寸变体
#### 变体类 \[!toc]
* `.button--primary`
* `.button--secondary`
* `.button--tertiary`
* `.button--outline`
* `.button--ghost`
* `.button--danger`
* `.button--danger-soft`
#### 修饰符类 \[!toc]
* `.button--icon-only`
* `.button--icon-only.button--sm`
* `.button--icon-only.button--lg`
### 交互状态
按钮同时支持 CSS 伪类与 data 属性:
* **Hover**:`:hover` 或 `[data-hovered="true"]`
* **Active/Pressed**:`:active` 或 `[data-pressed="true"]`(含缩放变换)
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]`(显示焦点环)
* **Disabled**:`:disabled` 或 `[aria-disabled="true"]`(降低透明度,禁用指针事件)
* **Pending**:`[data-pending]`(加载期间禁用指针事件)
## API 参考
### Button
| Prop | 类型 | 默认值 | 描述 |
| ------------ | --------------------------------------------------------------------------------------------- | ----------- | -------------------------- |
| `variant` | `'primary' \| 'secondary' \| 'tertiary' \| 'outline' \| 'ghost' \| 'danger' \| 'danger-soft'` | `'primary'` | 视觉样式变体 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 按钮尺寸 |
| `fullWidth` | `boolean` | `false` | 是否占满容器宽度 |
| `isDisabled` | `boolean` | `false` | 是否禁用 |
| `isPending` | `boolean` | `false` | 是否处于加载状态 |
| `isIconOnly` | `boolean` | `false` | 是否仅包含图标 |
| `onPress` | `(e: PressEvent) => void` | - | 按下时的回调 |
| `children` | `React.ReactNode \| (values: ButtonRenderProps) => React.ReactNode` | - | 按钮内容或 render prop |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Render Props
使用 render prop 模式时,提供以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | --------- |
| `isPending` | `boolean` | 是否处于加载状态 |
| `isPressed` | `boolean` | 是否正在按下 |
| `isHovered` | `boolean` | 是否悬停 |
| `isFocused` | `boolean` | 是否聚焦 |
| `isFocusVisible` | `boolean` | 是否显示焦点指示器 |
| `isDisabled` | `boolean` | 是否禁用 |
## 相关案例
## 相关组件
## Related Components
* **Popover**: Displays content in context with a trigger
* **Tooltip**: Contextual information on hover or focus
* **Form**: Form validation and submission handling
# CloseButton 关闭按钮
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/close-button
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(buttons)/close-button.mdx
> 用于关闭对话框、模态框或 dismiss 内容的按钮组件
## 用法
```tsx
import { CloseButton } from "@heroui/react";
```
```tsx
import {CloseButton} from "@heroui/react";
export function Default() {
return ;
}
```
## 示例
### 交互
```tsx
"use client";
import {CloseButton} from "@heroui/react";
import {useState} from "react";
export function Interactive() {
const [count, setCount] = useState(0);
return (
setCount(count + 1)} />
已点击:{count} 次
);
}
```
### 自定义图标
```tsx
import {CircleXmark, Xmark} from "@gravity-ui/icons";
import {CloseButton} from "@heroui/react";
export function WithCustomIcon() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {CloseButton} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 CloseButton 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.close-button {
@apply bg-red-100 text-red-800 hover:bg-red-200;
}
.close-button--custom {
@apply rounded-full border-2 border-red-300;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
CloseButton 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/close-button.css)):
#### 基础类 \[!toc]
* `.close-button` - 基础组件样式
#### 变体类 \[!toc]
* `.close-button--default` - 默认变体
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Hover**:`:hover` 或 `[data-hovered="true"]`
* **Active/Pressed**:`:active` 或 `[data-pressed="true"]`
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:`:disabled` 或 `[aria-disabled="true"]`
## API 参考
### CloseButton
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ----------------------- | --------------- | ------------- |
| `variant` | `"default"` | `"default"` | 按钮视觉变体 |
| `children` | `ReactNode \| function` | ` ` | 显示内容(默认为关闭图标) |
| `onPress` | `() => void` | - | 按下时的回调 |
| `isDisabled` | `boolean` | `false` | 是否禁用 |
### React Aria Button Props
CloseButton 继承所有 React Aria Button 属性。常用属性包括:
| Prop | 类型 | 描述 |
| ------------------ | -------- | ----------- |
| `aria-label` | `string` | 屏幕阅读器的无障碍标签 |
| `aria-labelledby` | `string` | 标注按钮的元素 ID |
| `aria-describedby` | `string` | 描述按钮的元素 ID |
### Render Props
使用 render prop 模式时,提供以下值:
| Prop | 类型 | 描述 |
| ------------ | --------- | ---- |
| `isHovered` | `boolean` | 是否悬停 |
| `isPressed` | `boolean` | 是否按下 |
| `isFocused` | `boolean` | 是否聚焦 |
| `isDisabled` | `boolean` | 是否禁用 |
## 相关组件
## Related Components
* **Alert**: Display important messages and notifications
* **AlertDialog**: Critical confirmations requiring user attention
* **Chip**: Compact elements for tags and filters
# ToggleButtonGroup 切换按钮组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/toggle-button-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(buttons)/toggle-button-group.mdx
> 将多个 ToggleButton 组合为统一控件,允许用户选择单个或多个选项。
## 用法
```tsx
import { ToggleButtonGroup, ToggleButton } from '@heroui/react';
```
```tsx
import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function Basic() {
return (
);
}
```
## 组件结构
导入 ToggleButtonGroup 组件,并通过点语法访问所有子部分。
```tsx
import { ToggleButtonGroup, ToggleButton } from '@heroui/react';
export default () => (
First
Second
Third
);
```
## 示例
### 尺寸
```tsx
import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function Sizes() {
return (
);
}
```
### 方向
```tsx
import {Bold, Italic, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function Orientation() {
return (
);
}
```
### 宽度充满
```tsx
import {
Bold,
Italic,
Strikethrough,
TextAlignCenter,
TextAlignLeft,
TextAlignRight,
Underline,
} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function FullWidth() {
return (
左对齐
居中
右对齐
);
}
```
### 禁用
```tsx
import {Bold, Italic, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function Disabled() {
return (
);
}
```
### 无分隔线
省略按钮中的 ` ` 组件即可。
```tsx
import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function WithoutSeparator() {
return (
);
}
```
### 分离模式
使用 `isDetached` 让按钮之间留出间隔,而不是彼此连接。
```tsx
import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function Attached() {
return (
);
}
```
### 选择模式
使用 `selectionMode="single"` 实现互斥选择,或使用 `selectionMode="multiple"` 实现独立切换。
```tsx
import {
Bold,
Italic,
Strikethrough,
TextAlignCenter,
TextAlignLeft,
TextAlignRight,
Underline,
} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
export function SelectionMode() {
return (
);
}
```
### 受控组件
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [selectedKeys, setSelectedKeys] = useState(new Set(["bold"]));
return (
已选:
{selectedKeys.size > 0 ? [...selectedKeys].join(", ") : "无"}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Bold, Italic, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup} from "@heroui/react";
const toggleClass =
"rounded-lg text-muted data-[selected=true]:bg-accent-soft data-[selected=true]:text-accent-soft-foreground";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.toggle-button-group {
@apply rounded-lg;
}
.toggle-button-group__separator {
@apply opacity-25;
}
.toggle-button-group--full-width {
@apply w-full;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ToggleButtonGroup 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/toggle-button-group.css)):
#### 基础与布局类 \[!toc]
* `.toggle-button-group` - 容器基础样式
* `.toggle-button-group--horizontal` - 水平方向
* `.toggle-button-group--vertical` - 垂直方向
* `.toggle-button-group--full-width` - 全宽修饰符
* `.toggle-button-group__separator` - 按钮之间的分隔线元素
#### 修饰符类 \[!toc]
* `.toggle-button-group--detached` - 分离模式(按钮间有间隔)
## API 参考
### ToggleButtonGroup
继承自 [React Aria ToggleButtonGroup](https://react-aria.adobe.com/ToggleButtonGroup)。
| Prop | 类型 | 默认值 | 描述 |
| ------------------------ | ---------------------------- | -------------- | --------------------- |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | 是否允许选中一个或多个按钮 |
| `selectedKeys` | `Iterable` | - | 受控的选中状态 |
| `defaultSelectedKeys` | `Iterable` | - | 默认选中 key(非受控) |
| `onSelectionChange` | `(keys: Set) => void` | - | 选中变化时调用 |
| `disallowEmptySelection` | `boolean` | `false` | 是否禁止清空所有选中 |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | 布局方向 |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 传递给子 ToggleButton 的尺寸 |
| `isDetached` | `boolean` | `false` | 按钮是否以间隔分离显示 |
| `fullWidth` | `boolean` | `false` | 按钮组是否占满可用宽度 |
| `isDisabled` | `boolean` | `false` | 是否禁用组内全部按钮 |
| `className` | `string` | - | 额外的 CSS 类 |
### ToggleButtonGroup.Separator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | --------- |
| `className` | `string` | - | 额外的 CSS 类 |
## 说明
* ToggleButtonGroup 使用 React Context 将 `size` 传递给所有子 ToggleButton 组件
* 每个 ToggleButton 都必须有唯一 `id` prop,并与 `selectedKeys` / `defaultSelectedKeys` 中使用的 key 对应
* `isDisabled` prop 由 React Aria 原生处理,会禁用所有子 ToggleButton;单个按钮可通过设置 `isDisabled={false}` 覆盖
* 组件会自动处理按钮之间的圆角
* 在每个 ToggleButton(第一个除外)内添加 ` `,可在按钮之间显示分隔线
* 将 `disallowEmptySelection` 与 `selectionMode="single"` 一起使用,可确保始终有一个选项被选中
## 相关组件
## Related Components
* **ToggleButton**: Interactive toggle control for on/off states
* **ButtonGroup**: Group related buttons together
* **Button**: Allows a user to perform an action
# ToggleButton 切换按钮
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/toggle-button
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(buttons)/toggle-button.mdx
> 用于在开启/关闭或已选中/未选中状态之间切换的交互式切换控件。
## 用法
```tsx
import { ToggleButton } from '@heroui/react';
```
```tsx
import {Heart} from "@gravity-ui/icons";
import {ToggleButton} from "@heroui/react";
export function Basic() {
return (
点赞
);
}
```
## 示例
### 变体
```tsx
import {Heart} from "@gravity-ui/icons";
import {ToggleButton} from "@heroui/react";
export function Variants() {
return (
默认
幽灵
);
}
```
### 仅图标
```tsx
import {Bookmark, Heart} from "@gravity-ui/icons";
import {ToggleButton} from "@heroui/react";
export function IconOnly() {
return (
);
}
```
### 尺寸
```tsx
import {Heart} from "@gravity-ui/icons";
import {ToggleButton} from "@heroui/react";
export function Sizes() {
return (
);
}
```
### 禁用
```tsx
import {Heart, HeartFill} from "@gravity-ui/icons";
import {ToggleButton} from "@heroui/react";
export function Disabled() {
return (
点赞
点赞
);
}
```
### 受控组件
```tsx
"use client";
import {Heart, HeartFill} from "@gravity-ui/icons";
import {ToggleButton} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [isSelected, setIsSelected] = useState(false);
return (
{({isSelected: selected}) => (
<>
{selected ? : }
{selected ? "已点赞" : "点赞"}
>
)}
状态:{isSelected ? "已选" : "未选"}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Heart} from "@gravity-ui/icons";
import {ToggleButton} from "@heroui/react";
export function CustomStyles() {
return (
收藏文章
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.toggle-button {
@apply bg-purple-500 text-white;
}
.toggle-button--icon-only {
@apply rounded-lg;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ToggleButton 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/toggle-button.css)):
#### 基础与尺寸类 \[!toc]
* `.toggle-button` - 切换按钮基础样式
* `.toggle-button--sm` - 小尺寸变体
* `.toggle-button--md` - 中尺寸变体(默认)
* `.toggle-button--lg` - 大尺寸变体
#### 变体类 \[!toc]
* `.toggle-button--default` - 默认变体(填充背景)
* `.toggle-button--ghost` - 幽灵变体(透明背景)
#### 修饰符类 \[!toc]
* `.toggle-button--icon-only` - 仅图标切换按钮
* `.toggle-button--icon-only.toggle-button--sm` - 小尺寸仅图标
* `.toggle-button--icon-only.toggle-button--lg` - 大尺寸仅图标
### 交互状态
该切换按钮同时支持 CSS 伪类与 data 属性,以便灵活控制状态:
* **已选中**:`[data-selected="true"]`(强调色背景与前景)
* **悬停**:`:hover` 或 `[data-hovered="true"]`
* **激活/按下**:`:active` 或 `[data-pressed="true"]`(包含缩放变换)
* **聚焦**:`:focus-visible` 或 `[data-focus-visible="true"]`(显示焦点环)
* **禁用**:`:disabled` 或 `[aria-disabled="true"]`(降低透明度,禁用指针事件)
## API 参考
### ToggleButton
继承自 [React Aria ToggleButton](https://react-spectrum.adobe.com/react-aria/ToggleButton.html)。
| Prop | 类型 | 默认值 | 描述 |
| ----------------- | ------------------------------------------------------------------------- | ----------- | --------------- |
| `variant` | `'default' \| 'ghost'` | `'default'` | 视觉样式变体 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 切换按钮尺寸 |
| `isIconOnly` | `boolean` | `false` | 按钮是否仅包含图标 |
| `isSelected` | `boolean` | - | 受控的已选中状态 |
| `defaultSelected` | `boolean` | `false` | 默认已选中状态(非受控) |
| `isDisabled` | `boolean` | `false` | 是否禁用切换按钮 |
| `onChange` | `(isSelected: boolean) => void` | - | 已选中状态变化时调用的处理函数 |
| `onPress` | `(e: PressEvent) => void` | - | 按钮按下时调用的处理函数 |
| `children` | `React.ReactNode \| (values: ToggleButtonRenderProps) => React.ReactNode` | - | 按钮内容或渲染 prop |
### ToggleButtonRenderProps
使用渲染 prop 模式时,会提供以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | ------------ |
| `isSelected` | `boolean` | 按钮当前是否已选中 |
| `isPressed` | `boolean` | 按钮当前是否处于按下状态 |
| `isHovered` | `boolean` | 按钮是否处于悬停状态 |
| `isFocused` | `boolean` | 按钮是否处于聚焦状态 |
| `isFocusVisible` | `boolean` | 按钮是否应显示焦点指示 |
| `isDisabled` | `boolean` | 按钮是否被禁用 |
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Switch**: Toggle between two states
* **Checkbox**: Binary choice input control
# Dropdown 下拉菜单
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/dropdown
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(collections)/dropdown.mdx
> 展示用户可选择的操作或选项列表
## 用法
```tsx
import { Dropdown } from '@heroui/react';
```
```tsx
"use client";
import {Button, Dropdown, Label} from "@heroui/react";
export function Default() {
return (
操作
console.log(`Selected: ${key}`)}>
新建文件
复制链接
编辑文件
删除文件
);
}
```
## 组件结构
```tsx
import { Dropdown, Button, Label, Description, Header, Kbd, Separator } from '@heroui/react';
export default () => (
)
```
## 示例
### 带图标
```tsx
"use client";
import {FloppyDisk, FolderOpen, SquarePlus, TrashBin} from "@gravity-ui/icons";
import {Button, Dropdown, Kbd, Label} from "@heroui/react";
export function WithIcons() {
return (
操作
console.log(`Selected: ${key}`)}>
新建文件
N
打开文件
O
保存文件
S
删除文件
D
);
}
```
### 带描述
```tsx
"use client";
import {FloppyDisk, FolderOpen, SquarePlus, TrashBin} from "@gravity-ui/icons";
import {Button, Description, Dropdown, Kbd, Label} from "@heroui/react";
export function WithDescriptions() {
return (
操作
console.log(`Selected: ${key}`)}>
新建文件
创建新文件
N
打开文件
打开已有文件
O
保存文件
保存当前文件
S
删除文件
移至废纸篓
D
);
}
```
### 含禁用项
```tsx
"use client";
import {Bars, Pencil, SquarePlus, TrashBin} from "@gravity-ui/icons";
import {Button, Description, Dropdown, Header, Kbd, Label, Separator} from "@heroui/react";
export function WithDisabledItems() {
return (
console.log(`Selected: ${key}`)}
>
新建文件
创建新文件
N
编辑文件
进行修改
E
删除文件
移至废纸篓
D
);
}
```
### 分组选项
```tsx
"use client";
import {EllipsisVertical, Pencil, SquarePlus, TrashBin} from "@gravity-ui/icons";
import {Button, Description, Dropdown, Header, Kbd, Label, Separator} from "@heroui/react";
export function WithSections() {
return (
console.log(`Selected: ${key}`)}>
新建文件
创建新文件
N
编辑文件
进行修改
E
删除文件
移至废纸篓
D
);
}
```
### 多选
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Button, Dropdown, Header, Label} from "@heroui/react";
import {useState} from "react";
export function WithMultipleSelection() {
const [selected, setSelected] = useState(new Set(["apple"]));
return (
喜爱的水果
苹果
香蕉
樱桃
橙子
梨
);
}
```
### 受控组件
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Button, Dropdown, Label} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [selected, setSelected] = useState(new Set(["bold"]));
const selectedItems = Array.from(selected);
return (
已选:{selectedItems.length > 0 ? selectedItems.join("、") : "无"}
操作
粗体
斜体
下划线
);
}
```
### 受控展开状态
```tsx
"use client";
import {Button, Dropdown, Label} from "@heroui/react";
import {useState} from "react";
export function ControlledOpenState() {
const [open, setOpen] = useState(false);
return (
下拉菜单:{open ? "打开" : "关闭"}
操作
新建文件
打开文件
保存文件
删除文件
);
}
```
### 单选
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Button, Dropdown, Header, Label} from "@heroui/react";
import {useState} from "react";
export function WithSingleSelection() {
const [selected, setSelected] = useState(new Set(["apple"]));
return (
水果
苹果
香蕉
樱桃
橙子
梨
);
}
```
### 单选自定义指示器
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Button, Dropdown, Header, Label} from "@heroui/react";
import {useState} from "react";
export function SingleWithCustomIndicator() {
const [selected, setSelected] = useState(new Set(["apple"]));
const CustomCheckmarkIcon = (
);
return (
水果
{({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)}
苹果
{({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)}
香蕉
{({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)}
樱桃
{({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)}
橙子
{({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)}
梨
);
}
```
### 分组级选择
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Button, Dropdown, Header, Kbd, Label, Separator} from "@heroui/react";
import {useState} from "react";
export function WithSectionLevelSelection() {
const [textStyles, setTextStyles] = useState(new Set(["bold", "italic"]));
const [textAlignment, setTextAlignment] = useState(new Set(["left"]));
return (
样式
剪切
X
复制
C
粘贴
U
粗体
B
斜体
I
下划线
U
左对齐
A
居中
H
右对齐
D
);
}
```
### 带快捷键
```tsx
"use client";
import {Button, Dropdown, Kbd, Label} from "@heroui/react";
export function WithKeyboardShortcuts() {
return (
操作
console.log(`Selected: ${key}`)}>
新建
N
打开
O
保存
S
删除
D
);
}
```
### 含子菜单
```tsx
"use client";
import {Button, Dropdown, Label} from "@heroui/react";
export function WithSubmenus() {
return (
分享
console.log(`Selected: ${key}`)}>
复制链接
Facebook
其他
WhatsApp
Telegram
Discord
Email
工作邮箱
个人邮箱
);
}
```
### 自定义子菜单指示器
```tsx
"use client";
import {ArrowRight} from "@gravity-ui/icons";
import {Button, Dropdown, Label} from "@heroui/react";
export function WithCustomSubmenuIndicator() {
return (
分享
console.log(`Selected: ${key}`)}>
复制链接
Facebook
更多选项
WhatsApp
Telegram
Email
工作邮箱
个人邮箱
Discord
其他(默认指示器)
SMS
);
}
```
### 自定义触发器
```tsx
import {ArrowRightFromSquare, Gear, Persons} from "@gravity-ui/icons";
import {Avatar, Dropdown, Label} from "@heroui/react";
export function CustomTrigger() {
return (
JD
JD
Jane Doe
jane@example.com
仪表盘
个人资料
设置
);
}
```
### 长按触发
```tsx
import {Button, Dropdown, Label} from "@heroui/react";
export function LongPressTrigger() {
return (
长按
新建文件
打开文件
保存文件
删除文件
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Button, Dropdown, Label} from "@heroui/react";
export function CustomStyles() {
return (
操作
重命名
复制
删除
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Dropdown 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.dropdown {
@apply flex flex-col gap-1;
}
.dropdown__trigger {
@apply outline-none;
}
.dropdown__popover {
@apply rounded-lg border border-border bg-overlay p-2;
}
.dropdown__menu {
@apply flex flex-col gap-1;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Dropdown 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/dropdown.css)):
#### 基础类 \[!toc]
* `.dropdown` - Dropdown 根容器
* `.dropdown__trigger` - 触发 Dropdown 的按钮或元素
* `.dropdown__popover` - Popover 容器
* `.dropdown__menu` - Popover 内的菜单容器
#### 状态类 \[!toc]
* `.dropdown__trigger[data-focus-visible="true"]` - 触发器聚焦状态
* `.dropdown__trigger[data-disabled="true"]` - 触发器禁用状态
* `.dropdown__trigger[data-pressed="true"]` - 触发器按下状态
* `.dropdown__popover[data-entering]` - 进入动画状态
* `.dropdown__popover[data-exiting]` - 退出动画状态
* `.dropdown__menu[data-selection-mode="single"]` - 单选模式
* `.dropdown__menu[data-selection-mode="multiple"]` - 多选模式
### 菜单组件类
Dropdown 使用 Menu、MenuItem 与 MenuSection 作为底层组件。以下类名也可用于自定义:
#### 菜单类 \[!toc]
* `.menu` - 菜单容器([menu.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/menu.css))
* `[data-slot="separator"]` - 菜单内的分隔线元素
#### 菜单项类 \[!toc]
* `.menu-item` - 菜单项容器([menu-item.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/menu-item.css))
* `.menu-item__indicator` - 选中指示器(对勾或圆点)
* `[data-slot="menu-item-indicator--checkmark"]` - 对勾指示器 SVG
* `[data-slot="menu-item-indicator--dot"]` - 圆点指示器 SVG
* `.menu-item__indicator--submenu` - 子菜单指示器(箭头)
* `.menu-item--default` - 默认样式变体
* `.menu-item--danger` - 危险样式变体
#### 菜单项状态类 \[!toc]
* `.menu-item[data-focus-visible="true"]` - 聚焦状态(键盘焦点)
* `.menu-item[data-focus="true"]` - 聚焦状态
* `.menu-item[data-pressed]` - 按下状态
* `.menu-item[data-hovered]` - 悬停状态
* `.menu-item[data-selected="true"]` - 选中状态
* `.menu-item[data-disabled]` - 禁用状态
* `.menu-item[data-has-submenu="true"]` - 带子菜单的项
* `.menu-item[data-selection-mode="single"]` - 单选模式
* `.menu-item[data-selection-mode="multiple"]` - 多选模式
* `.menu-item[aria-checked="true"]` - 已勾选(ARIA)
* `.menu-item[aria-selected="true"]` - 已选中(ARIA)
#### 菜单分区类 \[!toc]
* `.menu-section` - 菜单分区容器([menu-section.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/menu-section.css))
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Hover**:触发器与菜单项上 `:hover` 或 `[data-hovered="true"]`
* **Focus**:触发器与菜单项上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:触发器与菜单项上 `:disabled` 或 `[data-disabled="true"]`
* **Pressed**:触发器与菜单项上 `:active` 或 `[data-pressed="true"]`
* **Selected**:菜单项上 `[data-selected="true"]` 或 `[aria-selected="true"]`
## API 参考
### Dropdown
| Prop | 类型 | 默认值 | 描述 |
| -------------- | --------------------------- | --------- | ------------------- |
| `isOpen` | `boolean` | - | 设置菜单展开状态(受控)。 |
| `defaultOpen` | `boolean` | - | 设置菜单默认展开状态(非受控)。 |
| `onOpenChange` | `(isOpen: boolean) => void` | - | 展开状态变化时调用的事件处理函数。 |
| `trigger` | `"press" \| "longPress"` | `"press"` | 触发菜单的交互类型。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | Dropdown 内容。 |
### Dropdown.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | --- | ------------------- |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 触发器内容或渲染函数。 |
使用 Button 作为触发器时,同样支持所有 [Button](https://react-spectrum.adobe.com/react-aria/Button.html) props。
### Dropdown.Popover
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------- |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | 相对于触发器的 Popover 位置。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 子内容。 |
同样支持所有 [Popover](https://react-spectrum.adobe.com/react-aria/Popover.html) props。
### Dropdown.Menu
| Prop | 类型 | 默认值 | 描述 |
| --------------------- | ---------------------------------- | -------- | ------------------- |
| `selectionMode` | `"single" \| "multiple" \| "none"` | `"none"` | 是否启用单选、多选或不启用选择。 |
| `selectedKeys` | `Iterable` | - | 当前选中的 key(受控)。 |
| `defaultSelectedKeys` | `Iterable` | - | 初始选中的 key(非受控)。 |
| `onSelectionChange` | `(keys: Selection) => void` | - | 选中变化时调用的事件处理函数。 |
| `disabledKeys` | `Iterable` | - | 禁用项的 key。 |
| `onAction` | `(key: Key) => void` | - | 激活菜单项时调用的事件处理函数。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 菜单内容。 |
同样支持所有 [Menu](https://react-spectrum.adobe.com/react-aria/Menu.html#menu) props。
### Dropdown.Section
| Prop | 类型 | 默认值 | 描述 |
| --------------------- | --------------------------- | --- | ------------------- |
| `selectionMode` | `"single" \| "multiple"` | - | 该分组内菜单项的选择模式。 |
| `selectedKeys` | `Iterable` | - | 当前选中的 key(受控)。 |
| `defaultSelectedKeys` | `Iterable` | - | 初始选中的 key(非受控)。 |
| `onSelectionChange` | `(keys: Selection) => void` | - | 选中变化时调用的事件处理函数。 |
| `disabledKeys` | `Iterable` | - | 禁用项的 key。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 分组内容。 |
同样支持所有 [MenuSection](https://react-spectrum.adobe.com/react-aria/Menu.html#menusection) props。
### Dropdown.Item
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | ----------- | ------------------- |
| `id` | `Key` | - | 菜单项唯一标识。 |
| `textValue` | `string` | - | 用于首字母导航的文本内容。 |
| `variant` | `"default" \| "danger"` | `"default"` | 菜单项视觉变体。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 菜单项内容或渲染函数。 |
同样支持所有 [MenuItem](https://react-spectrum.adobe.com/react-aria/Menu.html#menuitem) props。
### Dropdown.ItemIndicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | ------------- | ------------------- |
| `type` | `"checkmark" \| "dot"` | `"checkmark"` | 指示器类型。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 自定义指示器内容或渲染函数。 |
使用渲染函数时,会传入以下值:
| Prop | 类型 | 描述 |
| ----------------- | --------- | ------------ |
| `isSelected` | `boolean` | 该项是否选中。 |
| `isIndeterminate` | `boolean` | 该项是否处于不确定状态。 |
### Dropdown.SubmenuIndicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------- |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 自定义指示器内容。 |
### Dropdown.SubmenuTrigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------- |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 子菜单触发器内容。 |
同样支持所有 [SubmenuTrigger](https://react-spectrum.adobe.com/react-aria/Menu.html#submenutrigger) props。
### Render Props
在 Dropdown.Item 中使用渲染函数时,会传入以下值:
| Prop | 类型 | 描述 |
| ------------ | --------- | ----------- |
| `isSelected` | `boolean` | 该项是否选中。 |
| `isFocused` | `boolean` | 该项是否聚焦。 |
| `isDisabled` | `boolean` | 该项是否禁用。 |
| `isPressed` | `boolean` | 该项是否处于按下状态。 |
## 无障碍
Dropdown 组件实现 ARIA 菜单模式,并提供:
* 完整键盘导航(方向键、Home/End、首字母导航)
* 屏幕阅读器对操作与选中变化的播报
* 合理的焦点管理
* 禁用态支持
* 长按交互支持
* 子菜单导航
更多信息见 [React Aria Menu 文档](https://react-spectrum.adobe.com/react-aria/Menu.html#menu)。
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Popover**: Displays content in context with a trigger
* **Separator**: Visual divider between content
# ListBox 列表框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/list-box
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(collections)/list-box.mdx
> 列表框展示一组选项,并允许用户选择一个或多个。
## 用法
```tsx
import { ListBox } from '@heroui/react';
```
```tsx
import {Avatar, Description, Label, ListBox} from "@heroui/react";
export function Default() {
return (
B
Bob
bob@heroui.com
F
Fred
fred@heroui.com
M
Martha
martha@heroui.com
);
}
```
## 组件结构
```tsx
import { ListBox, Label, Description, Header } from '@heroui/react';
export default () => (
)
```
## 示例
### 含禁用项
```tsx
"use client";
import {Pencil, SquarePlus, TrashBin} from "@gravity-ui/icons";
import {Description, Header, Kbd, Label, ListBox, Separator, Surface} from "@heroui/react";
export function WithDisabledItems() {
return (
alert(`已选项目:${key}`)}
>
新建文件
创建新文件
N
编辑文件
进行修改
E
删除文件
移至废纸篓
D
);
}
```
### 分组选项
```tsx
"use client";
import {Pencil, SquarePlus, TrashBin} from "@gravity-ui/icons";
import {Description, Header, Kbd, Label, ListBox, Separator, Surface} from "@heroui/react";
export function WithSections() {
return (
alert(`已选项目:${key}`)}
>
新建文件
创建新文件
N
编辑文件
进行修改
E
删除文件
移至废纸篓
D
);
}
```
### 多选
```tsx
import {Avatar, Description, Label, ListBox, Surface} from "@heroui/react";
export function MultiSelect() {
return (
B
Bob
bob@heroui.com
F
Fred
fred@heroui.com
M
Martha
martha@heroui.com
);
}
```
### 受控组件
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Check} from "@gravity-ui/icons";
import {Avatar, Description, Label, ListBox, Surface} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [selected, setSelected] = useState(new Set(["1"]));
const selectedItems = Array.from(selected);
return (
B
Bob
bob@heroui.com
{({isSelected}) => (isSelected ? : null)}
F
Fred
fred@heroui.com
{({isSelected}) => (isSelected ? : null)}
M
Martha
martha@heroui.com
{({isSelected}) => (isSelected ? : null)}
已选:{selectedItems.length > 0 ? selectedItems.join("、") : "无"}
);
}
```
### 虚拟滚动
ListBox 通过 [Virtualizer](https://react-aria.adobe.com/Virtualizer) 支持虚拟化,仅渲染视口内可见行,从而高效展示大数据集。
```tsx
"use client";
import {Description, Label, ListBox, ListLayout, Virtualizer} from "@heroui/react";
interface User {
id: number;
name: string;
email: string;
}
export function Virtualization() {
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(n: number): User[] {
const users: User[] = [];
for (let i = 0; i < n; 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,
});
}
return users;
}
const users = generateUsers(1000);
return (
{(user) => (
{user.name}
{user.email}
)}
);
}
```
### 自定义勾选图标
```tsx
"use client";
import {Check} from "@gravity-ui/icons";
import {Avatar, Description, Label, ListBox, Surface} from "@heroui/react";
export function CustomCheckIcon() {
return (
B
Bob
bob@heroui.com
{({isSelected}) => (isSelected ? : null)}
F
Fred
fred@heroui.com
{({isSelected}) => (isSelected ? : null)}
M
Martha
martha@heroui.com
{({isSelected}) => (isSelected ? : null)}
);
}
```
### 渲染函数
```tsx
"use client";
import {Avatar, Description, Label, ListBox} from "@heroui/react";
export function RenderFunction() {
return (
}
selectionMode="single"
>
}
textValue="Bob"
>
B
Bob
bob@heroui.com
}
textValue="Fred"
>
F
Fred
fred@heroui.com
}
textValue="Martha"
>
M
Martha
martha@heroui.com
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Avatar, Description, Label, ListBox} from "@heroui/react";
export function CustomStyles() {
return (
B
Bob
bob@heroui.com
F
Fred
fred@heroui.com
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.list-box {
@apply rounded-lg border border-border bg-surface p-2;
}
.list-box-item {
@apply rounded px-2 py-1 cursor-pointer;
}
.list-box-item--danger {
@apply text-danger;
}
.list-box-item__indicator {
@apply text-accent;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ListBox 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/list-box.css)):
#### 基础类 \[!toc]
* `.list-box` - ListBox 根容器
* `.list-box-item` - 单个列表项
* `.list-box-item__indicator` - 选中指示图标
* `.list-box-section` - 用于分组的区块容器
#### 变体类 \[!toc]
* `.list-box--default` - 默认变体样式
* `.list-box--danger` - 危险变体样式
* `.list-box-item--default` - 列表项默认变体
* `.list-box-item--danger` - 列表项危险变体
#### 状态类 \[!toc]
* `.list-box-item[data-selected="true"]` - 选中状态
* `.list-box-item[data-focus-visible="true"]` - 聚焦状态
* `.list-box-item[data-disabled="true"]` - 禁用状态
* `.list-box-item__indicator[data-visible="true"]` - 指示器可见状态
### 交互状态
该组件同时支持 CSS 伪类与 data 属性:
* **悬停**:列表项上 `:hover` 或 `[data-hovered="true"]`
* **聚焦**:列表项上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **已选中**:列表项上 `[data-selected="true"]`
* **禁用**:列表项上 `:disabled` 或 `[data-disabled="true"]`
## API 参考
### ListBox
| Prop | 类型 | 默认值 | 描述 |
| --------------------- | -------------------------------------------------------------------------- | ----------- | --------------------- |
| `aria-label` | `string` | - | ListBox 的无障碍标签。 |
| `aria-labelledby` | `string` | - | 标注 ListBox 的元素 id。 |
| `selectionMode` | `"none" \| "single" \| "multiple"` | `"single"` | 选择行为。 |
| `selectedKeys` | `Selection` | - | 受控的选中 key。 |
| `defaultSelectedKeys` | `Selection` | - | 初始选中 key。 |
| `onSelectionChange` | `(keys: Selection) => void` | - | 选中变化时调用的事件处理函数。 |
| `disabledKeys` | `Iterable` | - | 禁用项的 key。 |
| `onAction` | `(key: Key) => void` | - | 激活某项时调用的事件处理函数。 |
| `variant` | `"default" \| "danger"` | `"default"` | 视觉变体。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | ListBox 项与分组。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### ListBox.Item
| Prop | 类型 | 默认值 | 描述 |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | --------------------- |
| `id` | `Key` | - | 列表项唯一标识。 |
| `textValue` | `string` | - | 用于无障碍与首字母导航的文本值。 |
| `isDisabled` | `boolean` | `false` | 是否禁用该项。 |
| `variant` | `"default" \| "danger"` | `"default"` | 视觉变体。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 列表项内容或渲染函数。 |
| `render` | `(props: DetailedHTMLProps \| React.JSX.IntrinsicElements[keyof React.JSX.IntrinsicElements], renderProps: ListBoxItemRenderProps) => ReactElement` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### ListBox.ItemIndicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | --- | ------------------- |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 自定义指示器内容或渲染函数。 |
### ListBox.Section
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | -------------------- |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 分组内容,包含 Header 与列表项。 |
### RenderProps
在 ListBox.Item 或 ListBox.ItemIndicator 中使用渲染函数时,会传入以下值:
| Prop | 类型 | 描述 |
| ------------ | --------- | ----------- |
| `isSelected` | `boolean` | 该项是否选中。 |
| `isFocused` | `boolean` | 该项是否聚焦。 |
| `isDisabled` | `boolean` | 该项是否禁用。 |
| `isPressed` | `boolean` | 该项是否处于按下状态。 |
### ListLayout
| Name | 类型 | 默认值 | 描述 |
| ------------------------ | --------------------- | --- | ------------------------------------------- |
| `rowHeight` | `number \| undefined` | 48 | 行固定高度(px)。 |
| `estimatedRowHeight` | `number \| undefined` | — | 行高可变时的估算高度。 |
| `headingHeight` | `number \| undefined` | 48 | 分组标题固定高度(px)。 |
| `estimatedHeadingHeight` | `number \| undefined` | — | 标题高度可变时的估算高度。 |
| `loaderHeight` | `number \| undefined` | 48 | 加载器元素固定高度(px)。该加载器用于在根级或嵌套行/分组中渲染「加载更多」等内容。 |
| `dropIndicatorThickness` | `number \| undefined` | 2 | 放置指示线厚度。 |
| `gap` | `number \| undefined` | 0 | 项之间的间距。 |
| `padding` | `number \| undefined` | 0 | 列表内边距。 |
## 无障碍
ListBox 组件实现 ARIA listbox 模式,并提供:
* 完整键盘导航支持
* 屏幕阅读器对选中变化的播报
* 合理的焦点管理
* 禁用状态支持
* 首字母导航(typeahead)搜索能力
更多信息见 [React Aria ListBox 文档](https://react-spectrum.adobe.com/react-aria/ListBox.html)。
## 相关组件
## Related Components
* **Select**: Dropdown select control
* **ComboBox**: Text input with searchable dropdown list
* **Avatar**: Display user profile images
# TagGroup 标签组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/tag-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(collections)/tag-group.mdx
> 可聚焦的标签列表,支持键盘导航、选择与移除。
## 用法
```tsx
import { TagGroup } from '@heroui/react';
```
```tsx
"use client";
import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons";
import {Tag, TagGroup} from "@heroui/react";
export function TagGroupBasic() {
return (
资讯
旅行
游戏
购物
);
}
```
## 组件结构
```tsx
import { TagGroup, Tag, Label, Description, ErrorMessage } from '@heroui/react';
export default () => (
)
```
## 示例
### 尺寸
```tsx
"use client";
import {Label, Tag, TagGroup} from "@heroui/react";
export function TagGroupSizes() {
return (
小
资讯
旅行
游戏
中
资讯
旅行
游戏
大
资讯
旅行
游戏
);
}
```
### 变体
```tsx
"use client";
import {Label, Tag, TagGroup} from "@heroui/react";
export function TagGroupVariants() {
return (
默认
资讯
旅行
游戏
表面
资讯
旅行
游戏
);
}
```
### 禁用
```tsx
"use client";
import {Description, Label, Tag, TagGroup} from "@heroui/react";
export function TagGroupDisabled() {
return (
已禁用的标签
资讯
旅行
游戏
部分标签已禁用
禁用的键
资讯
旅行
游戏
通过 disabledKeys 属性禁用的标签
);
}
```
### 选择模式
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Description, Label, Tag, TagGroup} from "@heroui/react";
import {useState} from "react";
export function TagGroupSelectionModes() {
const [singleSelected, setSingleSelected] = useState>(new Set(["news"]));
const [multipleSelected, setMultipleSelected] = useState>(
new Set(["news", "travel"]),
);
return (
setSingleSelected(keys)}
>
单选
资讯
旅行
游戏
购物
选择一个分类
setMultipleSelected(keys)}
>
多选
资讯
旅行
游戏
购物
选择多个分类
);
}
```
### 受控组件
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Description, Label, Tag, TagGroup} from "@heroui/react";
import {useState} from "react";
export function TagGroupControlled() {
const [selected, setSelected] = useState>(new Set(["news", "travel"]));
return (
setSelected(keys)}
>
分类(受控)
资讯
旅行
游戏
购物
已选:{Array.from(selected).length > 0 ? Array.from(selected).join(", ") : "无"}
);
}
```
### 带错误信息
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Description, ErrorMessage, Label, Tag, TagGroup} from "@heroui/react";
import {useMemo, useState} from "react";
export function TagGroupWithErrorMessage() {
const [selected, setSelected] = useState>(new Set());
const isInvalid = useMemo(() => Array.from(selected).length === 0, [selected]);
return (
setSelected(keys)}
>
设施
洗衣
健身中心
停车
游泳池
早餐
{isInvalid ? "请至少选择一个分类" : "已选:" + Array.from(selected).join(", ")}
{!!isInvalid && <>请至少选择一个分类>}
);
}
```
### 带列表数据
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Avatar, Description, EmptyState, Label, Tag, TagGroup, useListData} from "@heroui/react";
export function TagGroupWithListData() {
type User = {
id: string;
name: string;
avatar: string;
fallback: string;
};
const list = useListData({
getKey: (item) => item.id,
initialItems: [
{
avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
fallback: "F",
id: "fred",
name: "Fred",
},
{
avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
fallback: "M",
id: "michael",
name: "Michael",
},
{
avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
fallback: "J",
id: "jane",
name: "Jane",
},
{
avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
fallback: "A",
id: "alice",
name: "Alice",
},
{
avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
fallback: "B",
id: "bob",
name: "Bob",
},
{
avatar: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/black.jpg",
fallback: "C",
id: "charlie",
name: "Charlie",
},
],
initialSelectedKeys: new Set(["fred", "michael"]),
});
const onRemove = (keys: Set) => {
list.remove(...keys);
};
return (
list.setSelectedKeys(keys)}
>
团队成员
暂无团队成员 }
>
{(user) => (
{user.fallback}
{user.name}
)}
为项目选择团队成员
{list.selectedKeys !== "all" && Array.from(list.selectedKeys).length > 0 && (
已选:
{Array.from(list.selectedKeys).map((key) => {
const user = list.getItem(key);
if (!user) return null;
return (
{user.fallback}
{user.name}
);
})}
)}
);
}
```
### 带前缀
```tsx
"use client";
import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons";
import {Avatar, Description, Label, Tag, TagGroup} from "@heroui/react";
export function TagGroupWithPrefix() {
return (
带图标
News
Travel
Gaming
Shopping
带图标的标签
带头像
F
Fred
M
Michael
J
Jane
带头像的标签
);
}
```
### 带移除按钮
```tsx
"use client";
import type {Key} from "@heroui/react";
import {CircleXmarkFill} from "@gravity-ui/icons";
import {Description, EmptyState, Label, Tag, TagGroup} from "@heroui/react";
import {useState} from "react";
export function TagGroupWithRemoveButton() {
type TagItem = {id: string; name: string};
const [tags, setTags] = useState([
{id: "news", name: "资讯"},
{id: "travel", name: "旅行"},
{id: "gaming", name: "游戏"},
{id: "shopping", name: "购物"},
]);
const [frameworks, setFrameworks] = useState([
{id: "react", name: "React"},
{id: "vue", name: "Vue"},
{id: "angular", name: "Angular"},
{id: "svelte", name: "Svelte"},
]);
const onRemoveTags = (keys: Set) => {
setTags(tags.filter((tag) => !keys.has(tag.id)));
};
const onRemoveFrameworks = (keys: Set) => {
setFrameworks(frameworks.filter((framework) => !keys.has(framework.id)));
};
return (
默认移除按钮
未找到分类 }
>
{(tag) => (
{tag.name}
)}
点击 × 移除标签
自定义移除按钮
未找到框架 }
>
{(tag) => (
{(renderProps) => (
<>
{tag.name}
{!!renderProps.allowsRemoving && (
)}
>
)}
)}
带图标的自定义移除按钮
);
}
```
### 渲染函数
```tsx
"use client";
import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons";
import {Tag, TagGroup} from "@heroui/react";
export function RenderFunction() {
return (
}
selectionMode="single"
>
资讯
旅行
游戏
购物
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons";
import {Tag, TagGroup} from "@heroui/react";
const tagClass =
"group/tag gap-1.5 rounded-full border border-neutral-300/80 bg-white/80 px-2.5 py-1 text-sm font-medium text-neutral-700 shadow-sm ring-1 ring-black/5 backdrop-blur-sm transition-colors data-[selected=true]:border-neutral-500 data-[selected=true]:bg-neutral-900 data-[selected=true]:text-neutral-50 dark:border-neutral-600/80 dark:bg-neutral-900/60 dark:text-neutral-200 dark:ring-white/10 dark:data-[selected=true]:border-neutral-400 dark:data-[selected=true]:bg-neutral-100 dark:data-[selected=true]:text-neutral-900";
const iconClass =
"size-4 shrink-0 text-neutral-500 group-data-[selected=true]/tag:text-current dark:text-neutral-400";
export function CustomStyles() {
return (
资讯
旅行
游戏
购物
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.tag-group {
@apply flex flex-col gap-2;
}
.tag-group__list {
@apply flex flex-wrap gap-2;
}
.tag {
@apply rounded-full px-3 py-1;
}
.tag__remove-button {
@apply ml-1;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
TagGroup 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tag-group.css) 与 [tag.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tag.css)):
#### 基础类 \[!toc]
* `.tag-group` - TagGroup 根容器
* `.tag-group__list` - 标签列表容器
* `.tag` - 标签基础样式
* `.tag__remove-button` - 移除按钮触发器
#### 插槽类 \[!toc]
* `.tag-group [slot="description"]` - Description 插槽样式
* `.tag-group [slot="errorMessage"]` - ErrorMessage 插槽样式
#### 尺寸类 \[!toc]
* `.tag--sm` - 小尺寸标签
* `.tag--md` - 中尺寸标签(默认)
* `.tag--lg` - 大尺寸标签
#### 变体类 \[!toc]
* `.tag--default` - 默认变体
* `.tag--surface` - 带 Surface 背景的变体
#### 状态类 \[!toc]
* `.tag[data-selected="true"]` - 选中状态
* `.tag[data-disabled="true"]` - 禁用状态
* `.tag[data-hovered="true"]` - 悬停状态
* `.tag[data-pressed="true"]` - 按下状态
* `.tag[data-focus-visible="true"]` - 聚焦状态(键盘焦点)
### 交互状态
该组件同时支持 CSS 伪类与 data 属性:
* **悬停**:标签上 `:hover` 或 `[data-hovered="true"]`
* **聚焦**:标签上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **按下**:标签上 `:active` 或 `[data-pressed="true"]`
* **已选中**:标签上 `[data-selected="true"]` 或 `[aria-selected="true"]`
* **禁用**:标签上 `:disabled` 或 `[data-disabled="true"]`
## API 参考
### TagGroup
| Prop | 类型 | 默认值 | 描述 |
| --------------------- | ----------------------------------------------------------------- | ----------- | --------------------- |
| `selectionMode` | `"none" \| "single" \| "multiple"` | `"none"` | 允许的选择类型。 |
| `selectedKeys` | `Selection` | - | 当前选中的 key(受控)。 |
| `defaultSelectedKeys` | `Selection` | - | 初始选中的 key(非受控)。 |
| `onSelectionChange` | `(keys: Selection) => void` | - | 选中变化时调用的事件处理函数。 |
| `disabledKeys` | `Iterable` | - | 禁用标签的 key。 |
| `isDisabled` | `boolean` | - | 是否禁用整个 TagGroup。 |
| `onRemove` | `(keys: Set) => void` | - | 移除标签时调用的事件处理函数。 |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 组内标签尺寸。 |
| `variant` | `"default" \| "surface"` | `"default"` | 标签视觉变体。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | TagGroup 内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### TagGroup.List
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------------------------------------------------------------------------- | --- | --------------------- |
| `items` | `Iterable` | - | 标签列表要展示的数据项。 |
| `renderEmptyState` | `() => ReactNode` | - | 列表为空时的渲染函数。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 标签列表内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Tag
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ---------------------------------------------------------------------- | --- | --------------------- |
| `id` | `Key` | - | 标签唯一标识。 |
| `textValue` | `string` | - | 标签内容的字符串表示,用于无障碍。 |
| `isDisabled` | `boolean` | - | 是否禁用该标签。 |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 标签内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
**提示:** `size`、`variant` 由父级 `TagGroup` 继承,无法在单个 `Tag` 上直接设置。
### Tag.RemoveButton
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------- |
| `className` | `string` | - | 额外的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 自定义移除按钮内容(默认为关闭图标)。 |
**提示:** `Tag.RemoveButton` 支持类似 `SearchField.ClearButton` 的定制方式。当为 `TagGroup` 提供 `onRemove` 时:
* **自动渲染**:若 `Tag` 的子节点中未包含自定义 `Tag.RemoveButton`,会自动渲染默认移除按钮。
* **自定义按钮**:若在 `Tag` 下提供了自定义 `Tag.RemoveButton`,将替换自动渲染的按钮。
* **自定义图标**:可向 `Tag.RemoveButton` 传入自定义子内容(如图标)以改变外观。
**示例 — 自动渲染(默认)**:
```tsx
News
{/* Remove button is automatically rendered */}
```
**示例 — 自定义 RemoveButton(带图标)**:
```tsx
News
```
**示例 — 在 render props 中使用自定义 RemoveButton**:
```tsx
{(renderProps) => (
<>
News
{!!renderProps.allowsRemoving && (
)}
>
)}
```
### RenderProps
在 TagGroup.List 中使用渲染函数时,会传入以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | ------------ |
| `isSelected` | `boolean` | 标签是否选中。 |
| `isDisabled` | `boolean` | 标签是否禁用。 |
| `isHovered` | `boolean` | 标签是否悬停。 |
| `isPressed` | `boolean` | 标签是否按下。 |
| `isFocused` | `boolean` | 标签是否聚焦。 |
| `isFocusVisible` | `boolean` | 标签是否为可见键盘焦点。 |
## 相关组件
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **ErrorMessage**: Displays validation error messages for components with validation support
# ColorArea 颜色区域
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/color-area
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(colors)/color-area.mdx
> 二维颜色选择器,允许用户从渐变区域中选择颜色
## 用法
```tsx
import { ColorArea } from '@heroui/react';
```
```tsx
import {ColorArea} from "@heroui/react";
export function ColorAreaBasic() {
return (
);
}
```
## 组件结构
```tsx
import { ColorArea } from '@heroui/react';
export default () => (
);
```
## 示例
### 带定位点
```tsx
import {ColorArea} from "@heroui/react";
export function ColorAreaWithDots() {
return (
);
}
```
### 色彩空间与通道
使用 `colorSpace` 设置颜色空间(RGB、HSL、HSB),使用 `xChannel`/`yChannel` 自定义各轴显示的颜色通道。
```tsx
"use client";
import type {ColorSpace, Key} from "@heroui/react";
import {ColorArea, Label, ListBox, Select, parseColor} from "@heroui/react";
import {useState} from "react";
type ColorChannel = "hue" | "saturation" | "brightness" | "lightness" | "red" | "green" | "blue";
interface ChannelOption {
id: ColorChannel;
name: string;
}
const colorSpaces: Array<{id: ColorSpace; name: string}> = [
{id: "rgb", name: "RGB"},
{id: "hsl", name: "HSL"},
{id: "hsb", name: "HSB"},
];
const channelsBySpace: Record = {
hsb: [
{id: "hue", name: "Hue"},
{id: "saturation", name: "Saturation"},
{id: "brightness", name: "Brightness"},
],
hsl: [
{id: "hue", name: "Hue"},
{id: "saturation", name: "Saturation"},
{id: "lightness", name: "Lightness"},
],
rgb: [
{id: "red", name: "Red"},
{id: "green", name: "Green"},
{id: "blue", name: "Blue"},
],
};
export function ColorAreaSpaceAndChannels() {
const [colorSpace, setColorSpace] = useState("hsb");
const [color, setColor] = useState(() => parseColor("hsb(219, 58%, 93%)"));
const channels = channelsBySpace[colorSpace];
const defaultX = colorSpace === "rgb" ? "blue" : "saturation";
const defaultY =
colorSpace === "rgb" ? "green" : colorSpace === "hsl" ? "lightness" : "brightness";
const [xChannel, setXChannel] = useState(defaultX);
const [yChannel, setYChannel] = useState(defaultY);
const handleColorSpaceChange = (newSpace: Key | null) => {
if (!newSpace) return;
const space = newSpace as ColorSpace;
setColorSpace(space);
// Reset channels to appropriate defaults for the new color space
if (space === "rgb") {
setXChannel("blue");
setYChannel("green");
} else if (space === "hsl") {
setXChannel("saturation");
setYChannel("lightness");
} else {
setXChannel("saturation");
setYChannel("brightness");
}
};
// Filter out the other channel from options (can't have same channel on both axes)
const xChannelOptions = channels.filter((c) => c.id !== yChannel);
const yChannelOptions = channels.filter((c) => c.id !== xChannel);
return (
{/* Controls */}
{/* Color Space Select */}
Color Space
{colorSpaces.map((space) => (
{space.name}
))}
{/* X Channel Select */}
value && setXChannel(value as ColorChannel)}
>
X Axis
{xChannelOptions.map((channel) => (
{channel.name}
))}
{/* Y Channel Select */}
value && setYChannel(value as ColorChannel)}
>
Y Axis
{yChannelOptions.map((channel) => (
{channel.name}
))}
{/* Color Area */}
{/* Color Value Display */}
{color.toString(colorSpace)}
);
}
```
### 禁用
```tsx
import {ColorArea} from "@heroui/react";
export function ColorAreaDisabled() {
return (
);
}
```
### 受控组件
```tsx
"use client";
import type {Color} from "@heroui/react";
import {ColorArea, ColorSwatch, parseColor} from "@heroui/react";
import {useState} from "react";
export function ColorAreaControlled() {
const [color, setColor] = useState(parseColor("#9B80FF"));
return (
Current color:{" "}
{color ? color.toString("hex") : "(empty)"}
);
}
```
### 渲染函数
```tsx
"use client";
import {ColorArea} from "@heroui/react";
export function RenderFunction() {
return (
}
>
} />
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ColorArea} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ColorArea 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.color-area {
@apply rounded-3xl;
}
.color-area__thumb {
@apply size-5 border-4;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ColorArea 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-area.css)):
#### 基础类 \[!toc]
* `.color-area` - 渐变背景与内阴影的基础样式
* `.color-area--show-dots` - 添加点阵网格覆盖层以精确选色
#### 元素类 \[!toc]
* `.color-area__thumb` - 可拖拽的滑块指示器
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Disabled**:`[data-disabled="true"]`
* **Focus**:`[data-focus-visible="true"]`
* **Dragging**:`[data-dragging="true"]`(仅 thumb)
## API 参考
### ColorArea
继承自 [React Aria ColorArea](https://react-spectrum.adobe.com/react-aria/ColorArea.html)。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ---------------------------------------------------------------------------- | -------------- | -------------------------- |
| `value` | `string \| Color` | - | 当前颜色值(受控) |
| `defaultValue` | `string \| Color` | - | 默认颜色值(非受控) |
| `onChange` | `(color: Color) => void` | - | 拖拽过程中颜色变化时的回调 |
| `onChangeEnd` | `(color: Color) => void` | - | 用户停止拖拽时的回调 |
| `xChannel` | `ColorChannel` | `"saturation"` | 水平轴的颜色通道 |
| `yChannel` | `ColorChannel` | `"brightness"` | 垂直轴的颜色通道 |
| `colorSpace` | `ColorSpace` | - | 通道使用的颜色空间 |
| `isDisabled` | `boolean` | `false` | 是否禁用 |
| `showDots` | `boolean` | `false` | 是否显示点阵网格覆盖层 |
| `className` | `string` | - | 附加 CSS 类 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### ColorArea.Thumb
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------------------------------------------------------- | --- | -------------------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `style` | `CSSProperties \| ((renderProps) => CSSProperties)` | - | 内联样式或 render props 函数 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
## 相关组件
## Related Components
* **ColorSwatch**: Visual preview of a color value
* **ColorSwatchPicker**: Color swatch selection from a list of colors
* **ColorField**: Input for entering color values with hex format
# ColorField 颜色输入框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/color-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(colors)/color-field.mdx
> 基于 React Aria ColorField 的颜色输入字段,支持标签、描述与验证
## 用法
```tsx
import { ColorField, parseColor } from '@heroui/react';
```
```tsx
"use client";
import type {Color} from "@heroui/react";
import {ColorField, ColorSwatch, Label, parseColor} from "@heroui/react";
import {useState} from "react";
export function Basic() {
const [color, setColor] = useState(parseColor("#0485F7"));
return (
颜色
);
}
```
## 组件结构
```tsx
import {ColorField, Label, ColorSwatch, Description, FieldError, parseColor} from '@heroui/react';
export default () => (
)
```
> **ColorField** 将标签、颜色输入、描述与错误合并为单个无障碍组件。
## 示例
### 变体
ColorField.Group 组件支持两种视觉变体:
* **`primary`**(默认)- 标准样式带阴影,适用于大多数场景
* **`secondary`** - 低强调变体无阴影,适用于 Surface 组件内
```tsx
import {ColorField, Label} from "@heroui/react";
export function Variants() {
return (
主要变体
次要变体
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 组件内使用时,在 ColorField.Group 上使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
import {ColorField, Description, Label, Surface} from "@heroui/react";
export function OnSurface() {
return (
主题色
选择你的主题色
);
}
```
### 带描述
```tsx
import {ColorField, Description, Label} from "@heroui/react";
export function WithDescription() {
return (
主色
输入品牌主色
强调色
用于高亮与行动按钮
);
}
```
### 必填字段
```tsx
import {ColorField, Description, Label} from "@heroui/react";
export function Required() {
return (
品牌色
主题色
必填项
);
}
```
### 禁用
```tsx
"use client";
import {ColorField, Description, Label} from "@heroui/react";
export function Disabled() {
return (
颜色
该颜色字段已禁用
颜色
该颜色字段已禁用
);
}
```
### 宽度充满
```tsx
import {ColorField, Label} from "@heroui/react";
export function FullWidth() {
return (
品牌色
主题色
);
}
```
### 表单校验
将 `isInvalid` 与 `FieldError` 一起使用以显示验证消息。
```tsx
import {ColorField, FieldError, Label} from "@heroui/react";
export function Invalid() {
return (
颜色
请输入有效的十六进制颜色
背景色
颜色格式无效,请使用十六进制(例如 #FF5733)
);
}
```
### 分量编辑
ColorField 支持通过设置 `colorSpace` 与 `channel` 属性编辑单个颜色通道(hue、saturation、lightness、red、green、blue、alpha)。
```tsx
"use client";
import type {Color} from "@heroui/react";
import {ColorField, ColorSwatch, Label, parseColor} from "@heroui/react";
import {useState} from "react";
export function ChannelEditing() {
const [color, setColor] = useState(parseColor("#7F007F"));
return (
分别编辑 HSL 通道:
色相
饱和度
%
明度
%
当前:{color ? color.toString("hex") : "(空)"}
);
}
```
### 受控组件
控制值以与其他组件或状态管理同步。
```tsx
"use client";
import type {Color} from "@heroui/react";
import {Button, ColorField, ColorSwatch, Description, Label, parseColor} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(parseColor("#0485F7"));
return (
颜色
当前值:{value ? value.toString("hex") : "(空)"}
setValue(parseColor("#EF4444"))}>
设为红色
setValue(parseColor("#10B981"))}>
设为绿色
setValue(null)}>
清空
);
}
```
### 表单示例
包含验证与提交处理的完整表单示例。
```tsx
"use client";
import type {Color} from "@heroui/react";
import {Button, ColorField, ColorSwatch, Description, Form, Label} from "@heroui/react";
import {useState} from "react";
export function FormExample() {
const [value, setValue] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
if (!value) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("已提交颜色:", {color: value.toString("hex")});
setValue(null);
setIsSubmitting(false);
}, 1500);
};
return (
品牌色
选择品牌主色
{isSubmitting ? "保存中…" : "保存颜色"}
);
}
```
### 渲染函数
```tsx
"use client";
import type {Color} from "@heroui/react";
import {ColorField, ColorSwatch, Label, parseColor} from "@heroui/react";
import {useState} from "react";
export function RenderFunction() {
const [color, setColor] = useState(parseColor("#0485F7"));
return (
}
value={color}
onChange={setColor}
>
颜色
}>
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import type {Color} from "@heroui/react";
import {ColorField, ColorSwatch, Description, Label, parseColor} from "@heroui/react";
import {useState} from "react";
export function CustomStyles() {
const [color, setColor] = useState(parseColor("#6366F1"));
return (
强调色
应用于按钮、链接和焦点环。
);
}
```
### 全局 CSS
ColorField 默认样式较少。覆盖 `.color-field` 类以自定义容器样式。
```css
@layer components {
.color-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;
}
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
* `.color-field` – 最小样式的根容器(`flex flex-col gap-1`)
> **Note:** 子组件([Label](/docs/react/components/label)、[Description](/docs/react/components/description)、[FieldError](/docs/react/components/field-error))有各自的 CSS 类与样式。请参阅各自文档了解自定义选项。ColorField.Group 样式见下方 API 参考。
### 交互状态
ColorField 根据状态自动管理以下 data 属性:
* **Invalid**:`[data-invalid="true"]` 或 `[aria-invalid="true"]` - 无效时自动隐藏 description slot
* **Required**:`[data-required="true"]` - `isRequired` 为 true 时应用
* **Disabled**:`[data-disabled="true"]` - `isDisabled` 为 true 时应用
* **Focus Within**:`[data-focus-within="true"]` - 任一子 input 聚焦时应用
## API 参考
### ColorField
ColorField 继承 React Aria [ColorField](https://react-aria.adobe.com/ColorField.md) 组件的所有属性。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------- | ------- | ---------------------------------------- |
| `children` | `React.ReactNode \| (values: ColorFieldRenderProps) => React.ReactNode` | - | 子组件(Label、ColorField.Group 等)或 render 函数 |
| `className` | `string \| (values: ColorFieldRenderProps) => string` | - | CSS 类,支持 render props |
| `style` | `React.CSSProperties \| (values: ColorFieldRenderProps) => React.CSSProperties` | - | 内联样式,支持 render props |
| `fullWidth` | `boolean` | `false` | 是否占满容器宽度 |
| `id` | `string` | - | 元素唯一标识符 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
#### Value Props
| Prop | 类型 | 默认值 | 描述 |
| -------------- | -------------------------------- | --- | -------- |
| `value` | `Color \| null` | - | 当前值(受控) |
| `defaultValue` | `Color \| null` | - | 默认值(非受控) |
| `onChange` | `(color: Color \| null) => void` | - | 值变化时的回调 |
#### Channel Props
| Prop | 类型 | 默认值 | 描述 |
| ------------ | -------------- | --- | ------------------------- |
| `colorSpace` | `ColorSpace` | - | 提供 `channel` 时颜色字段操作的颜色空间 |
| `channel` | `ColorChannel` | - | 要编辑的颜色通道。未提供时编辑 hex 值 |
#### Validation Props
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ---------------------------------------------------------------- | ---------- | ------------------------ |
| `isRequired` | `boolean` | `false` | 表单提交前是否必须输入 |
| `isInvalid` | `boolean` | - | 值是否无效 |
| `validate` | `(value: Color) => ValidationError \| true \| null \| undefined` | - | 自定义验证函数 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 表单验证还是 ARIA 属性 |
#### State Props
| Prop | 类型 | 默认值 | 描述 |
| ----------------- | --------- | --- | ---------- |
| `isDisabled` | `boolean` | - | 是否禁用 |
| `isReadOnly` | `boolean` | - | 是否可选中但不可更改 |
| `isWheelDisabled` | `boolean` | - | 是否禁用滚轮更改值 |
#### Form Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------- | --- | ---------------------- |
| `name` | `string` | - | HTML 表单提交时 input 元素的名称 |
| `autoFocus` | `boolean` | - | 渲染时是否自动聚焦 |
#### Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------- | --- | ------------ |
| `aria-label` | `string` | - | 无可见标签时的无障碍标签 |
| `aria-labelledby` | `string` | - | 标注此字段的元素 ID |
| `aria-describedby` | `string` | - | 描述此字段的元素 ID |
| `aria-details` | `string` | - | 包含附加详情的元素 ID |
### Composition Components
ColorField 与以下需单独导入并直接使用的组件配合:
* **Label** - 来自 `@heroui/react` 的字段标签组件
* **ColorField.Group** - 颜色输入组组件(见下方文档)
* **ColorField.Input** - ColorField.Group 内的 input 元素
* **ColorField.Prefix** / **ColorField.Suffix** - 输入组的前缀与后缀 slot
* **ColorSwatch** - 来自 `@heroui/react` 的颜色预览组件
* **Description** - 来自 `@heroui/react` 的帮助文本组件
* **FieldError** - 来自 `@heroui/react` 的验证错误消息
每个组件有各自的 props API。在 ColorField 内直接使用它们进行组合:
```tsx
import {ColorField, Label, ColorSwatch, Description, FieldError, parseColor} from '@heroui/react';
Brand Color
Select your brand's primary color.
Please enter a valid color.
```
### Color Types
ColorField 使用 React Aria Components 的 `Color` 对象:
```tsx
import {parseColor} from '@heroui/react';
// Parse from hex string
const color = parseColor('#3B82F6');
// Get hex string from color
const hex = color.toString('hex'); // "#3b82f6"
// Get RGB values
const rgb = color.toString('rgb'); // "rgb(59, 130, 246)"
// Use in ColorField
{/* ... */}
```
### Render Props
对 `className`、`style` 或 `children` 使用 render props 时,可使用以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | ------------ |
| `isDisabled` | `boolean` | 字段是否禁用 |
| `isInvalid` | `boolean` | 字段是否当前无效 |
| `isReadOnly` | `boolean` | 字段是否只读 |
| `isRequired` | `boolean` | 字段是否必填 |
| `isFocused` | `boolean` | 字段是否当前聚焦 |
| `isFocusWithin` | `boolean` | 是否有子元素聚焦 |
| `isFocusVisible` | `boolean` | 焦点是否可见(键盘导航) |
### ColorField.Group
ColorField.Group 接受 React Aria `Group` 组件的所有属性,以及以下属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------ | ----------- | --------------------------------------------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind 类 |
| `fullWidth` | `boolean` | `false` | 颜色输入组是否占满容器宽度 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调无阴影,适用于 Surface 内 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### ColorField.Input
ColorField.Input 接受 React Aria `Input` 组件的所有属性,以及以下属性:
| Prop | 类型 | 默认值 | 描述 |
| ------------- | -------- | --- | ------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind 类 |
| `placeholder` | `string` | - | 为空时显示的占位文本 |
### ColorField.Prefix
ColorField.Prefix 接受标准 HTML `div` 属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind 类 |
| `children` | `ReactNode` | - | 前缀 slot 中显示的内容 |
### ColorField.Suffix
ColorField.Suffix 接受标准 HTML `div` 属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind 类 |
| `children` | `ReactNode` | - | 后缀 slot 中显示的内容 |
## ColorField.Group Styling
### Customizing the component classes
基础类驱动每个实例。使用 `@layer components` 一次性覆盖。
```css
@layer components {
.color-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;
}
}
.color-input-group__input {
@apply flex flex-1 items-center rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
}
.color-input-group__prefix,
.color-input-group__suffix {
@apply shrink-0 text-field-placeholder flex items-center;
}
}
```
### ColorField.Group CSS Classes
* `.color-input-group` – 根容器样式
* `.color-input-group__input` – Input 包装器样式
* `.color-input-group__prefix` – 前缀元素样式
* `.color-input-group__suffix` – 后缀元素样式
### ColorField.Group Interactive States
* **Hover**:`:hover` 或 `[data-hovered="true"]`
* **Focus Within**:`[data-focus-within="true"]` 或 `:focus-within`
* **Invalid**:`[data-invalid="true"]`(与 `aria-invalid` 同步)
* **Disabled**:`[data-disabled="true"]` 或 `[aria-disabled="true"]`
## 相关组件
## Related Components
* **ColorSwatch**: Visual preview of a color value
* **ColorSwatchPicker**: Color swatch selection from a list of colors
* **ColorPicker**: Composable color picker with popover
# ColorPicker 颜色选择器
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/color-picker
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(colors)/color-picker.mdx
> 可组合的颜色选择器,在多个颜色组件间同步颜色值
## 用法
```tsx
import {
ColorPicker,
ColorArea,
ColorSlider,
ColorSwatch,
ColorField,
ColorSwatchPicker,
} from '@heroui/react';
```
```tsx
import {ColorArea, ColorPicker, ColorSlider, ColorSwatch, Label} from "@heroui/react";
export function Basic() {
return (
选择颜色
色相
);
}
```
## 组件结构
ColorPicker 是组合多个颜色组件的可组合组件:
```tsx
import { ColorPicker, ColorArea, ColorSlider, ColorSwatch, Label } from '@heroui/react';
export default () => (
Pick a color
);
```
## 示例
### 受控组件
```tsx
"use client";
import {
Button,
ColorArea,
ColorField,
ColorPicker,
ColorSlider,
ColorSwatch,
ColorSwatchPicker,
Label,
parseColor,
} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function Controlled() {
const [color, setColor] = useState(parseColor("#325578"));
const colorPresets = [
"#ef4444",
"#f97316",
"#eab308",
"#22c55e",
"#06b6d4",
"#3b82f6",
"#8b5cf6",
"#ec4899",
"#f43f5e",
];
const shuffleColor = () => {
const randomHue = Math.floor(Math.random() * 360);
const randomSaturation = 50 + Math.floor(Math.random() * 50); // 50-100%
const randomLightness = 40 + Math.floor(Math.random() * 30); // 40-70%
setColor(parseColor(`hsl(${randomHue}, ${randomSaturation}%, ${randomLightness}%)`));
};
return (
选择颜色
{colorPresets.map((preset) => (
))}
已选:{color.toString("hex")}
);
}
```
### 带色板
```tsx
import {
ColorArea,
ColorPicker,
ColorSlider,
ColorSwatch,
ColorSwatchPicker,
Label,
} from "@heroui/react";
export function WithSwatches() {
const presets = [
"#ef4444",
"#f97316",
"#eab308",
"#22c55e",
"#06b6d4",
"#3b82f6",
"#8b5cf6",
"#ec4899",
"#f43f5e",
];
return (
品牌色
色相
{presets.map((preset) => (
))}
);
}
```
### 带输入字段
使用 `ColorField` 允许用户编辑单个颜色通道值,配合 `Select` 切换颜色空间。
```tsx
"use client";
import type {ColorChannel, ColorSpace} from "@heroui/react";
import {
ColorArea,
ColorField,
ColorPicker,
ColorSlider,
ColorSwatch,
Label,
ListBox,
Select,
} from "@heroui/react";
import {useState} from "react";
const CHANNEL_LABELS: Record = {
alpha: "透明度",
blue: "蓝",
brightness: "亮度",
green: "绿",
hue: "色相",
lightness: "明度",
red: "红",
saturation: "饱和度",
};
export function WithFields() {
const [colorSpace, setColorSpace] = useState("hsl");
const colorChannelsByColorSpace: Record = {
hsb: ["hue", "saturation", "brightness"],
hsl: ["hue", "saturation", "lightness"],
rgb: ["red", "green", "blue"],
};
return (
选择颜色
色相
setColorSpace(value as ColorSpace)}
>
{Object.keys(colorChannelsByColorSpace).map((space) => (
{space}
))}
{colorChannelsByColorSpace[colorSpace].map((channel) => (
))}
);
}
```
### 带滑块
使用多个 `ColorSlider` 组件调整颜色值的各通道。
```tsx
"use client";
import type {ColorChannel, ColorSpace} from "@heroui/react";
import {ColorPicker, ColorSlider, ColorSwatch, Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
const CHANNEL_LABELS: Record = {
alpha: "透明度",
blue: "蓝",
brightness: "亮度",
green: "绿",
hue: "色相",
lightness: "明度",
red: "红",
saturation: "饱和度",
};
export function WithSliders() {
const [colorSpace, setColorSpace] = useState("hsl");
const colorChannelsByColorSpace: Record = {
hsb: ["hue", "saturation", "brightness", "alpha"],
hsl: ["hue", "saturation", "lightness", "alpha"],
rgb: ["red", "green", "blue", "alpha"],
};
return (
选择颜色
setColorSpace(value as ColorSpace)}
>
{Object.keys(colorChannelsByColorSpace).map((space) => (
{space}
))}
{colorChannelsByColorSpace[colorSpace].map((channel: ColorChannel) => (
// @ts-expect-error - TypeScript can't correlate dynamic colorSpace with channel type
{CHANNEL_LABELS[channel]}
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ColorArea, ColorPicker, ColorSlider, ColorSwatch, Label} from "@heroui/react";
export function CustomStyles() {
return (
主题色
色相
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ColorPicker 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.color-picker {
@apply inline-flex;
}
.color-picker__trigger {
@apply inline-flex items-center gap-4 rounded-lg;
}
.color-picker__popover {
@apply p-4 rounded-xl;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ColorPicker 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-picker.css)):
#### 基础类 \[!toc]
* `.color-picker` - 基础容器
* `.color-picker__trigger` - 触发按钮
* `.color-picker__popover` - 弹出层容器
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:`:disabled` 或 `[data-disabled="true"]`
## API 参考
### ColorPicker
继承自 [React Aria ColorPicker](https://react-spectrum.adobe.com/react-aria/ColorPicker.html)。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ------------------------ | --- | -------------------------- |
| `value` | `string \| Color` | - | 当前颜色值(受控) |
| `defaultValue` | `string \| Color` | - | 默认颜色值(非受控) |
| `onChange` | `(color: Color) => void` | - | 颜色变化时的回调 |
| `children` | `React.ReactNode` | - | 颜色选择器内容(Trigger、Popover 等) |
| `className` | `string` | - | 附加 CSS 类 |
### ColorPicker.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------- | --- | ------------------ |
| `children` | `React.ReactNode \| ((renderProps) => React.ReactNode)` | - | 触发器内容或 render prop |
| `className` | `string` | - | 附加 CSS 类 |
### ColorPicker.Popover
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --------------- | -------- |
| `placement` | `Placement` | `"bottom left"` | 弹出层位置 |
| `children` | `React.ReactNode` | - | 弹出层内容 |
| `className` | `string` | - | 附加 CSS 类 |
### Related Types
#### Color
表示颜色值。完整 API 请参阅 [React Aria Color](https://react-spectrum.adobe.com/react-aria/ColorPicker.html#color)。
| Method | Description |
| ---------------------------------- | ----------------------------------- |
| `toString(format)` | 将颜色转换为指定格式的字符串(hex、rgb、hsl、hsb、css) |
| `toFormat(format)` | 将颜色转换为指定格式并返回新的 Color 对象 |
| `getChannelValue(channel)` | 返回指定通道的数值 |
| `withChannelValue(channel, value)` | 设置通道数值并返回新的 Color |
#### parseColor
```tsx
import { parseColor } from 'react-aria-components';
// Parse from string
const color = parseColor('#ff0000');
const hslColor = parseColor('hsl(0, 100%, 50%)');
```
## 相关组件
## Related Components
* **ColorArea**: 2D color picker for selecting colors from a gradient area
* **ColorSlider**: Slider for adjusting individual color channel values
* **ColorSwatch**: Visual preview of a color value
# ColorSlider 颜色滑块
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/color-slider
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(colors)/color-slider.mdx
> 颜色滑块允许用户调整颜色值的单个通道
## 用法
```tsx
import { ColorSlider, Label } from '@heroui/react';
```
```tsx
import {ColorSlider, Label} from "@heroui/react";
export function Basic() {
return (
色相
);
}
```
## 组件结构
```tsx
import { ColorSlider, Label } from '@heroui/react';
export default () => (
Hue
)
```
## 示例
### 禁用
```tsx
import {ColorSlider, Label} from "@heroui/react";
export function Disabled() {
return (
色相
);
}
```
### 垂直方向
```tsx
import {ColorSlider} from "@heroui/react";
export function Vertical() {
return (
);
}
```
### 受控组件
```tsx
"use client";
import {ColorSlider, ColorSwatch, Label} from "@heroui/react";
import {useState} from "react";
import {parseColor} from "react-aria-components";
export function Controlled() {
const [color, setColor] = useState(parseColor("hsl(200, 100%, 50%)"));
return (
色相
当前颜色:{color.toString("hsl")}
);
}
```
### HSL 通道
使用多个 ColorSlider 控制颜色值的不同通道。滑块可共享同一颜色值以构建完整颜色选择器。
```tsx
"use client";
import {ColorSlider, ColorSwatch, Label} from "@heroui/react";
import {useState} from "react";
import {parseColor} from "react-aria-components";
export function Channels() {
const [color, setColor] = useState(parseColor("hsl(0, 100%, 50%)"));
return (
色相
饱和度
明度
当前颜色:{color.toString("hsl")}
);
}
```
### 透明度通道
Alpha 通道滑块显示透明棋盘格图案,帮助可视化透明度级别。
```tsx
import {ColorSlider, Label} from "@heroui/react";
export function AlphaChannel() {
return (
透明度
);
}
```
### RGB 通道
也可使用 RGB 颜色空间的红、绿、蓝通道。
```tsx
"use client";
import {ColorSlider, ColorSwatch, Label} from "@heroui/react";
import {useState} from "react";
import {parseColor} from "react-aria-components";
export function RGBChannels() {
const [color, setColor] = useState(parseColor("rgb(255, 100, 50)"));
return (
红
绿
蓝
当前颜色:{color.toString("rgb")}
);
}
```
### 渲染函数
```tsx
"use client";
import {ColorSlider, Label} from "@heroui/react";
export function RenderFunction() {
return (
}
>
色相
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ColorSlider, Label} from "@heroui/react";
export function CustomStyles() {
return (
色相
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ColorSlider 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.color-slider {
@apply flex flex-col gap-2;
}
.color-slider__output {
@apply text-muted text-sm;
}
.color-slider__track {
@apply relative h-5 w-full rounded-full;
}
.color-slider__thumb {
@apply size-4 rounded-full border-3 border-white shadow-overlay;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ColorSlider 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-slider.css)):
#### 基础类 \[!toc]
* `.color-slider` - 基础滑块容器
* `.color-slider__output` - 显示当前值的输出元素
* `.color-slider__track` - 带颜色渐变的轨道元素
* `.color-slider__thumb` - 显示当前颜色的滑块元素
#### 状态类 \[!toc]
* `.color-slider[data-disabled="true"]` - 禁用滑块状态
* `.color-slider[data-orientation="vertical"]` - 垂直方向
* `.color-slider__thumb[data-dragging="true"]` - 滑块正在拖拽
* `.color-slider__thumb[data-focus-visible="true"]` - 滑块键盘聚焦
* `.color-slider__thumb[data-disabled="true"]` - 禁用滑块状态
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Hover**:滑块上的 `:hover` 或 `[data-hovered="true"]`
* **Focus**:滑块上的 `:focus-visible` 或 `[data-focus-visible="true"]`
* **Dragging**:滑块上的 `[data-dragging="true"]`
* **Disabled**:滑块或 thumb 上的 `:disabled` 或 `[data-disabled="true"]`
## API 参考
### ColorSlider
继承自 [React Aria ColorSlider](https://react-spectrum.adobe.com/react-aria/ColorSlider.html)。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ------------------------------------------------------------------------------ | -------------- | ------------------------------------------------------------------- |
| `channel` | `ColorChannel` | - | 滑块操作的颜色通道(hue、saturation、lightness、brightness、alpha、red、green、blue) |
| `colorSpace` | `ColorSpace` | - | 颜色空间(hsl、hsb、rgb)。默认为值的颜色空间 |
| `value` | `string \| Color` | - | 当前颜色值(受控) |
| `defaultValue` | `string \| Color` | - | 默认颜色值(非受控) |
| `onChange` | `(value: Color) => void` | - | 拖拽过程中值变化时的回调 |
| `onChangeEnd` | `(value: Color) => void` | - | 拖拽结束时的回调 |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | 滑块方向 |
| `isDisabled` | `boolean` | - | 是否禁用 |
| `name` | `string` | - | 表单提交时 input 元素的名称 |
| `aria-label` | `string` | - | 滑块的无障碍标签 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | 滑块内容或 render 函数 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### ColorSlider.Output
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | --- | --------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | 输出内容或 render 函数 |
### ColorSlider.Track
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------------------------- | --- | --------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `style` | `CSSProperties \| RenderFunction` | - | 内联样式或 render 函数 |
| `children` | `ReactNode \| RenderFunction` | - | 轨道内容或 render 函数 |
### ColorSlider.Thumb
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------------------------- | --- | --------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `style` | `CSSProperties \| RenderFunction` | - | 内联样式或 render 函数 |
| `children` | `ReactNode \| RenderFunction` | - | 滑块内容或 render 函数 |
### Render Props
使用 render 函数时,提供以下值:
| Prop | 类型 | 描述 |
| ------------- | ---------------------------- | ------- |
| `state` | `ColorSliderState` | 颜色滑块的状态 |
| `color` | `Color` | 当前颜色值 |
| `orientation` | `"horizontal" \| "vertical"` | 滑块方向 |
| `isDisabled` | `boolean` | 是否禁用 |
## 无障碍
ColorSlider 组件实现 ARIA slider 模式,提供:
* 完整键盘导航支持(方向键、Home、End、Page Up/Down)
* 值变化的屏幕阅读器播报
* 正确的焦点管理
* 禁用状态支持
* 通过隐藏 input 元素集成 HTML 表单
* 支持 locale 感知值格式化的国际化
更多信息请参阅 [React Aria ColorSlider 文档](https://react-spectrum.adobe.com/react-aria/ColorSlider.html)。
## 相关组件
## Related Components
* **ColorSwatch**: Visual preview of a color value
* **ColorSwatchPicker**: Color swatch selection from a list of colors
* **ColorPicker**: Composable color picker with popover
# ColorSwatchPicker 颜色色块选择器
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/color-swatch-picker
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(colors)/color-swatch-picker.mdx
> 允许用户从预置调色板中选择颜色的 swatch 列表
## 用法
```tsx
import { ColorSwatchPicker, parseColor } from '@heroui/react';
```
```tsx
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function Basic() {
return (
{colors.map((color) => (
))}
);
}
```
## 组件结构
```tsx
import { ColorSwatchPicker } from '@heroui/react';
export default () => (
);
```
## 示例
### 变体
```tsx
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function Variants() {
return (
圆形(默认)
{colors.map((color) => (
))}
方形
{colors.map((color) => (
))}
);
}
```
### 尺寸
```tsx
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
const sizes = ["xs", "sm", "md", "lg", "xl"] as const;
const SIZE_LABELS: Record<(typeof sizes)[number], string> = {
lg: "大",
md: "中",
sm: "小",
xl: "特大",
xs: "特小",
};
export function Sizes() {
return (
{sizes.map((size) => (
{SIZE_LABELS[size]}
{colors.map((color) => (
))}
))}
);
}
```
### 禁用
```tsx
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function Disabled() {
return (
{colors.map((color) => (
))}
);
}
```
### 堆叠布局
```tsx
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function StackLayout() {
return (
{colors.map((color) => (
))}
);
}
```
### 默认值
```tsx
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function DefaultValue() {
return (
{colors.map((color) => (
))}
);
}
```
### 受控组件
```tsx
"use client";
import {ColorSwatchPicker, parseColor} from "@heroui/react";
import {useState} from "react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function Controlled() {
const [value, setValue] = useState(parseColor("#F43F5E"));
return (
{colors.map((color) => (
))}
已选:{value.toString("hex")}
);
}
```
### 自定义指示器
```tsx
import {HeartFill} from "@gravity-ui/icons";
import {ColorSwatchPicker} from "@heroui/react";
export function CustomIndicator() {
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
return (
{colors.map((color) => (
))}
);
}
```
### 渲染函数
```tsx
"use client";
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function RenderFunction() {
return (
}>
{colors.map((color) => (
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ColorSwatchPicker} from "@heroui/react";
const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"];
export function CustomStyles() {
return (
{colors.map((color) => (
))}
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ColorSwatchPicker 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.color-swatch-picker {
@apply gap-4;
}
.color-swatch-picker__item {
@apply shadow-md;
}
.color-swatch-picker__swatch {
@apply border-2 border-white;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ColorSwatchPicker 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-swatch-picker.css)):
#### 基础与结构 \[!toc]
* `.color-swatch-picker` - 基础容器(flex 布局)
* `.color-swatch-picker__item` - 单个 swatch 包裹层
* `.color-swatch-picker__swatch` - swatch 视觉元素
#### 尺寸类 \[!toc]
* `.color-swatch-picker--xs` - 特小(16px)
* `.color-swatch-picker--sm` - 小(24px)
* `.color-swatch-picker--md` - 中(32px,默认)
* `.color-swatch-picker--lg` - 大(36px)
* `.color-swatch-picker--xl` - 特大(40px)
#### 形状变体 \[!toc]
* `.color-swatch-picker--circle` - 圆形(默认)
* `.color-swatch-picker--square` - 圆角方形
#### 布局类 \[!toc]
* `.color-swatch-picker--grid` - 横向换行网格(默认)
* `.color-swatch-picker--stack` - 纵向堆叠
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Hover**:`:hover` 或 `[data-hovered="true"]` - 缩放至 1.1(仅在未选中时)
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]` - 焦点环
* **Selected**:`[data-selected="true"]` - 与 swatch 同色的内边框
* **Disabled**:`[data-disabled="true"]` - 降低透明度
## API 参考
### ColorSwatchPicker
继承自 [React Aria ColorSwatchPicker](https://react-spectrum.adobe.com/react-aria/ColorSwatchPicker.html)。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ------------------------------------------------------------------------------------ | ---------- | -------------------------- |
| `value` | `string \| Color` | - | 当前选中颜色(受控) |
| `defaultValue` | `string \| Color` | - | 默认选中颜色(非受控) |
| `onChange` | `(value: Color) => void` | - | 选中变化时的回调 |
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | swatch 尺寸 |
| `variant` | `"circle" \| "square"` | `"circle"` | swatch 形状 |
| `layout` | `"grid" \| "stack"` | `"grid"` | 布局方向 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | ColorSwatchPicker.Item 元素 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### ColorSwatchPicker.Item
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ---------------------------------------------------------------------------------------- | ------- | --------------------------- |
| `color` | `string \| Color` | **必填** | swatch 颜色 |
| `isDisabled` | `boolean` | `false` | 是否禁用该项 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | ColorSwatchPicker.Swatch 元素 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### ColorSwatchPicker.Swatch
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
### parseColor
为方便使用,从 React Aria Components 重新导出 `parseColor` 函数:
```tsx
import { parseColor } from '@heroui/react';
// 解析十六进制颜色
const red = parseColor('#ff0000');
// 解析 RGB
const green = parseColor('rgb(0, 255, 0)');
// 解析 HSL
const blue = parseColor('hsl(240, 100%, 50%)');
```
## 相关组件
## Related Components
* **ColorSwatch**: Visual preview of a color value
* **ColorField**: Input for entering color values with hex format
* **ColorArea**: 2D color picker for selecting colors from a gradient area
# ColorSwatch 颜色色块
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/color-swatch
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(colors)/color-swatch.mdx
> 颜色值的视觉预览,并提供无障碍支持
## 用法
```tsx
import { ColorSwatch } from '@heroui/react';
```
```tsx
import {ColorSwatch} from "@heroui/react";
export function ColorSwatchBasic() {
return (
);
}
```
## 示例
### 尺寸
```tsx
import {ColorSwatch} from "@heroui/react";
export function ColorSwatchSizes() {
return (
);
}
```
### 形状
```tsx
import {ColorSwatch} from "@heroui/react";
export function ColorSwatchShapes() {
return (
);
}
```
### 透明度
```tsx
import {ColorSwatch} from "@heroui/react";
export function ColorSwatchTransparency() {
return (
);
}
```
### 渲染函数
可使用 `style` render prop 读取颜色值并创建自定义视觉效果。
```tsx
"use client";
import {ColorSwatch} from "@heroui/react";
export function RenderFunction() {
return (
);
}
```
### 无障碍
使用 `colorName` 为颜色提供自定义可访问名称,并使用 `aria-label` 补充颜色用途的上下文。
```tsx
import {ColorSwatch} from "@heroui/react";
export function ColorSwatchAccessibility() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {ColorSwatch} from "@heroui/react";
export function ColorSwatchCustomStyles() {
const colors = ["#0485F7", "#EF4444", "#F59E0B", "#10B981", "#D946EF"];
return (
{/* 发光效果 */}
发光效果
{colors.map((color) => (
({
boxShadow: `0 0 20px 2px ${color}`,
})}
/>
))}
{/* 渐变色块 */}
渐变
{colors.map((color) => (
({
background: `linear-gradient(135deg, ${c.toString("css")}, white)`,
})}
/>
))}
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ColorSwatch 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.color-swatch {
@apply border-2 border-white;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ColorSwatch 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-swatch.css)):
#### 基础类 \[!toc]
* `.color-swatch` - 基础 swatch 样式,透明区域使用棋盘格背景
#### 形状类 \[!toc]
* `.color-swatch--circle` - 圆形(默认)
* `.color-swatch--square` - 圆角方形
#### 尺寸类 \[!toc]
* `.color-swatch--xs` - 特小(16px)
* `.color-swatch--sm` - 小(24px)
* `.color-swatch--md` - 中(32px,默认)
* `.color-swatch--lg` - 大(36px)
* `.color-swatch--xl` - 特大(40px)
## API 参考
### ColorSwatch
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ------------------------------------------------------------------------------ | ---------- | ----------------------------- |
| `color` | `string \| Color` | - | 要展示的颜色值(hex、rgb、hsl 等) |
| `colorName` | `string` | - | 颜色的可访问名称(会覆盖自动生成的描述) |
| `className` | `string` | - | 附加 CSS 类 |
| `shape` | `"circle" \| "square"` | `"circle"` | swatch 形状 |
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | swatch 尺寸 |
| `style` | `CSSProperties \| ((renderProps) => CSSProperties)` | - | 行内样式,或带颜色访问能力的 render prop 函数 |
| `aria-label` | `string` | - | swatch 的无障碍标签 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Style Render
当把 `style` 作为函数传入时,会获得包含颜色对象在内的 render props:
```tsx
({
boxShadow: `0 4px 14px ${color.toString("css")}80`,
})}
/>
```
`color` 对象提供例如:
* `color.toString("css")` - 返回 CSS 颜色字符串
* `color.toString("hex")` - 返回十六进制颜色字符串
* `color.getChannelValue("alpha")` - 返回 alpha 通道数值
## 相关组件
## Related Components
* **ColorSwatchPicker**: Color swatch selection from a list of colors
* **ColorField**: Input for entering color values with hex format
* **ColorArea**: 2D color picker for selecting colors from a gradient area
# Slider 滑块
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/slider
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(controls)/slider.mdx
> Slider 允许用户在范围内选择一个或多个值。
## 用法
```tsx
import { Slider } from '@heroui/react';
```
```tsx
import {Label, Slider} from "@heroui/react";
export function Default() {
return (
音量
);
}
```
## 组件结构
```tsx
import { Slider, Label } from '@heroui/react';
export default () => (
)
```
`Slider.Steps` 和 `Slider.Marks` 均为可选组件,可按需独立组合。
## 示例
### 步进指示点
将 `Slider.Steps` 放在 `Slider.Track` 内,即可根据 `minValue`、`maxValue` 和 `step` 在每个步进位置显示一个圆点。即使不显示圆点,`step` 也会控制数值的步进间隔。
圆点从 `minValue` 开始排列。如果 `maxValue` 不在步进位置上,则不会在该端点额外添加圆点。范围无效或所需圆点超过 1,000 个时,不渲染指示点。
```tsx
import {Label, Slider} from "@heroui/react";
export function WithSteps() {
return (
音量
);
}
```
### 刻度标签
将 `Slider.Marks` 作为 `Slider.Track` 的同级元素紧随其后放置,并添加带有必填 `value` 的 `Slider.Mark` 子组件。每个标签根据对应数值定位,因此标签可以不等距排列,也不必与步进位置一致。
通过子内容自定义标签。未提供子内容时,标签会使用 Slider 的区域设置和 `formatOptions` 格式化数值。超出范围或数值非有限的标签不会渲染。刻度标签仅用于展示,不会改变数值或步进吸附行为。
```tsx
import {Label, Slider} from "@heroui/react";
export function WithMarks() {
return (
音量
关闭
轻声
响亮
最大
);
}
```
### 步进指示点与刻度标签
圆点和标签可以独立组合。步进支持小数;此示例使用 `step={0.1}`,并在 `0`、`0.5` 和 `1` 处显示标签。
```tsx
import {Label, Slider} from "@heroui/react";
export function WithStepsAndMarks() {
return (
音量
);
}
```
### 带指示点与标签的范围滑块
单个滑块控制点下,从最小值到当前选中值之间的圆点和标签会获得 `data-filled="true"`。范围滑块中,仅第一个和最后一个控制点之间的圆点和标签会获得该属性,包含两个端点。
```tsx
"use client";
import {Label, Slider} from "@heroui/react";
export function RangeWithStepsAndMarks() {
return (
范围
{({state}) => (
<>
{state.values.map((_, index) => (
))}
>
)}
);
}
```
### 垂直指示点与标签
圆点和标签会自动适配 `orientation="vertical"`。请为垂直滑块提供具有明确高度的容器。水平方向的指示点和标签也会根据区域设置适配从右到左的排列方向。
```tsx
import {Label, Slider} from "@heroui/react";
export function VerticalWithStepsAndMarks() {
return (
音量
);
}
```
### 禁用
```tsx
import {Label, Slider} from "@heroui/react";
export function Disabled() {
return (
音量
);
}
```
### 范围滑块结构
```tsx
import { Slider, Label } from '@heroui/react';
export default () => (
{({state}) => (
<>
{state.values.map((_, i) => (
))}
>
)}
)
```
### 垂直方向
```tsx
import {Label, Slider} from "@heroui/react";
export function Vertical() {
return (
音量
);
}
```
### 范围选择
```tsx
"use client";
import {Label, Slider} from "@heroui/react";
export function Range() {
return (
价格区间
{({state}) => (
<>
{state.values.map((_, i) => (
))}
>
)}
);
}
```
### 受控数值
使用 `value` 和 `onChange` 控制数值。
```tsx
"use client";
import {Label, Slider} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(25);
return (
{
if (typeof nextValue === "number") {
setValue(nextValue);
}
}}
>
音量
当前数值:{value}
);
}
```
### 自定义数值格式
使用 `formatOptions` 格式化显示的数值。此示例将数值格式化为美元。
```tsx
import {Label, Slider} from "@heroui/react";
export function CustomValueFormatting() {
return (
价格
);
}
```
### 自定义输出展示
自定义 `Slider.Output`,控制已选数值的展示方式。
```tsx
"use client";
import {Label, Slider} from "@heroui/react";
export function CustomOutput() {
return (
区间
{({state}) => state.values.map((_, i) => state.getThumbValueLabel(i)).join(" – ")}
{({state}) => (
<>
{state.values.map((_, i) => (
))}
>
)}
);
}
```
### 渲染函数
```tsx
"use client";
import {Label, Slider} from "@heroui/react";
export function RenderFunction() {
return (
}
>
音量
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Label, Slider} from "@heroui/react";
export function CustomStyles() {
return (
亮度
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.slider {
@apply gap-2;
}
.slider__output {
@apply text-sm text-muted;
}
.slider__track {
@apply relative h-2 w-full rounded-full bg-surface-secondary;
}
.slider__fill {
@apply absolute h-full rounded-full bg-accent;
}
.slider__thumb {
@apply size-4 rounded-full bg-accent border-2 border-background;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Slider 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/slider.css)):
#### 基础类 \[!toc]
* `.slider` - Slider 根容器
* `.slider__output` - 显示当前值的输出元素
* `.slider__track` - 包含填充、可选步进指示点与滑块控制点的轨道元素
* `.slider__fill` - 显示已选范围的填充元素
* `.slider__thumb` - 单个滑块控制点
* `.slider__steps` - 轨道内的装饰性步进指示点容器
* `.slider__step` - 单个步进指示点
* `.slider__marks` - 位于轨道之后的刻度标签容器
* `.slider__mark` - 根据数值定位的单个标签
#### 状态类 \[!toc]
* `.slider[data-disabled="true"]` - 禁用状态
* `.slider[data-orientation="vertical"]` - 纵向方向
* `.slider__thumb[data-dragging="true"]` - 滑块正在拖动
* `.slider__thumb[data-focus-visible="true"]` - 滑块键盘聚焦
* `.slider__thumb[data-disabled="true"]` - 滑块禁用状态
* `.slider__track[data-fill-start="true"]` - 填充从起点开始
* `.slider__track[data-fill-end="true"]` - 填充在终点结束
* `.slider__step[data-filled="true"]` - 步进指示点位于已选区间内
* `.slider__mark[data-filled="true"]` - 刻度标签位于已选区间内
### 指示点与标签的数据属性
| 属性 | 适用元素 | 描述 |
| ---------------------- | --------------------- | ---------------------------------------------------------------- |
| `data-slot` | 步进指示点容器、指示点、刻度标签容器、标签 | 分别为 `slider-steps`、`slider-step`、`slider-marks` 和 `slider-mark`。 |
| `data-value` | 步进指示点和刻度标签 | 对应的数值。 |
| `data-filled="true"` | 步进指示点和刻度标签 | 数值位于已选区间内,包含端点。 |
| `data-disabled="true"` | 步进指示点容器、指示点、刻度标签容器、标签 | 继承 Slider 的禁用状态。 |
| `data-orientation` | 步进指示点容器和刻度标签容器 | `horizontal` 或 `vertical`。 |
| `data-direction` | 步进指示点容器和刻度标签容器 | 根据区域设置确定的 `ltr` 或 `rtl`。 |
### 交互状态
该组件同时支持 CSS 伪类与 data 属性,以提供更灵活的状态控制:
* **悬停**:滑块上的 `:hover` 或 `[data-hovered="true"]`
* **聚焦**:滑块上的 `:focus-visible` 或 `[data-focus-visible="true"]`
* **拖动**:滑块上的 `[data-dragging="true"]`
* **禁用**:Slider 或滑块上的 `:disabled` 或 `[data-disabled="true"]`
## API 参考
### Slider
| Prop | 类型 | 默认值 | 描述 |
| ----------------- | ------------------------------------------------------------------------- | -------------- | ----------------------------------------- |
| `value` | `number \| number[]` | - | 当前值(受控)。 |
| `defaultValue` | `number \| number[]` | - | 默认值(非受控)。 |
| `onChange` | `(value: number \| number[]) => void` | - | 值变化时的事件处理函数。 |
| `onChangeEnd` | `(value: number \| number[]) => void` | - | 拖动结束时的事件处理函数。 |
| `minValue` | `number` | `0` | Slider 的最小值。 |
| `maxValue` | `number` | `100` | Slider 的最大值。 |
| `step` | `number` | `1` | 数值步进间隔;使用 `Slider.Steps` 时,也决定指示点之间的数值间隔。 |
| `formatOptions` | `Intl.NumberFormatOptions` | - | 数值标签的显示格式。 |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Slider 的方向。 |
| `isDisabled` | `boolean` | - | Slider 是否禁用。 |
| `aria-label` | `string` | - | Slider 的无障碍标签。 |
| `aria-labelledby` | `string` | - | 标注 Slider 的元素 ID。 |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | Slider 内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Slider.Output
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------- | --- | --------------------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 输出内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Slider.Track
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------ | --- | --------------------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 轨道内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Slider.Fill
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------- | --- | ---------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `style` | `CSSProperties` | - | 行内样式。 |
### Slider.Thumb
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ------------------------------------------------------------------------------ | --- | --------------------- |
| `index` | `number` | `0` | 滑块在 Slider 内的索引。 |
| `isDisabled` | `boolean` | - | 该滑块是否禁用。 |
| `name` | `string` | - | 输入元素名称,用于提交 HTML 表单。 |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 滑块内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Slider.Steps
在 `Slider.Track` 内根据根组件的范围和步进值渲染装饰性圆点。自动生成子元素,支持标准 DOM 属性和 ref。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------------------------------------------- | --- | --------------------- |
| `className` | `string` | - | 容器的额外 CSS 类。 |
| `style` | `CSSProperties` | - | 容器的行内样式。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Slider.Marks
包含 `Slider.Mark` 子组件。直接放置在 `Slider` 内,位于 `Slider.Track` 之后。支持标准 DOM 属性和 ref。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------------------------------------------- | --- | --------------------- |
| `children` | `ReactNode` | - | 刻度标签。 |
| `className` | `string` | - | 容器的额外 CSS 类。 |
| `style` | `CSSProperties` | - | 容器的行内样式。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Slider.Mark
在 `Slider.Marks` 内根据数值定位的标签。支持标准 DOM 属性和 ref。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------------------------------------------- | ------- | --------------------------------------------- |
| `value` | `number` | 必填 | 标签对应的数值;非有限值或超出范围的值不会渲染。 |
| `children` | `ReactNode` | 格式化后的数值 | 自定义标签内容;未提供时使用 Slider 的区域设置和 `formatOptions`。 |
| `className` | `string` | - | 额外的 CSS 类。 |
| `style` | `CSSProperties` | - | 额外的行内样式;数值定位由组件自动管理。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### RenderProps
对 `Slider.Output` 或 `Slider.Track` 使用渲染函数时,会提供以下值:
| Prop | 类型 | 描述 |
| -------------------- | ---------------------------- | --------------- |
| `state` | `SliderState` | Slider 的状态。 |
| `values` | `number[]` | 按滑块索引管理的数值。 |
| `getThumbValueLabel` | `(index: number) => string` | 返回指定滑块数值的字符串标签。 |
| `orientation` | `"horizontal" \| "vertical"` | Slider 的方向。 |
| `isDisabled` | `boolean` | Slider 是否禁用。 |
## 无障碍
Slider 组件实现 ARIA slider 模式,并提供:
* 完整的键盘导航支持(方向键、Home、End、Page Up/Down)
* 数值变化时的屏幕阅读器播报
* 合理的焦点管理
* 禁用状态支持
* 通过隐藏 input 元素与 HTML 表单集成
* 结合区域设置进行数值格式化的国际化支持
* 从右到左(RTL)语言支持
* 步进圆点仅用于装饰,对辅助技术隐藏,且不会增加 Tab 停靠点
* 刻度标签保留为可读文本,不会替代 Slider 的无障碍标签或数值播报
更多信息见 [React Aria Slider 文档](https://react-spectrum.adobe.com/react-aria/Slider.html)。
## 相关组件
## Related Components
* **Label**: Accessible label for form controls
* **Form**: Form validation and submission handling
* **Description**: Helper text for form fields
# Switch 开关
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/switch
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(controls)/switch.mdx
> 用于布尔状态的开关组件。
## 用法
```tsx
import { Switch, SwitchGroup, Label } from '@heroui/react';
```
```tsx
import {Switch} from "@heroui/react";
export function Basic() {
return (
启用通知
);
}
```
## 组件结构
```tsx
import { Switch, Label, Description } from '@heroui/react';
export default () => (
{/* 可选 */}
{/* 可选 */}
);
```
要对多个 Switch 进行分组,请使用 `SwitchGroup` 组件:
```tsx
import { Switch, SwitchGroup, Label } from '@heroui/react';
export default () => (
Option 1
Option 2
);
```
## 示例
### 尺寸
```tsx
import {Switch} from "@heroui/react";
export function Sizes() {
return (
小
中
大
);
}
```
### 带图标
```tsx
"use client";
import {
BellFill,
BellSlash,
Check,
Microphone,
MicrophoneSlash,
Moon,
Power,
Sun,
VolumeFill,
VolumeSlashFill,
} from "@gravity-ui/icons";
import {Switch} from "@heroui/react";
export function WithIcons() {
const icons = {
check: {
off: Power,
on: Check,
selectedControlClass: "bg-green-500/80",
},
darkMode: {
off: Moon,
on: Sun,
selectedControlClass: "",
},
microphone: {
off: Microphone,
on: MicrophoneSlash,
selectedControlClass: "bg-red-500/80",
},
notification: {
off: BellSlash,
on: BellFill,
selectedControlClass: "bg-purple-500/80",
},
volume: {
off: VolumeFill,
on: VolumeSlashFill,
selectedControlClass: "bg-blue-500/80",
},
};
return (
{Object.entries(icons).map(([key, value]) => (
{({isSelected}) => (
{isSelected ? (
) : (
)}
)}
))}
);
}
```
### 禁用
```tsx
import {Switch} from "@heroui/react";
export function Disabled() {
return (
启用通知
);
}
```
### 无标签
```tsx
import {Switch} from "@heroui/react";
export function WithoutLabel() {
return (
);
}
```
### 带描述
```tsx
import {Description, Switch} from "@heroui/react";
export function WithDescription() {
return (
公开资料
允许他人查看你的资料信息
);
}
```
### 默认选中
```tsx
import {Switch} from "@heroui/react";
export function DefaultSelected() {
return (
启用通知
);
}
```
### 受控组件
```tsx
"use client";
import {Switch} from "@heroui/react";
import React from "react";
export function Controlled() {
const [isSelected, setIsSelected] = React.useState(false);
return (
启用通知
开关{isSelected ? "已打开" : "已关闭"}
);
}
```
### 标签位置
```tsx
import {Switch} from "@heroui/react";
export function LabelPosition() {
return (
标签在后
标签在前
);
}
```
### 分组
```tsx
import {Switch, SwitchGroup} from "@heroui/react";
export function Group() {
return (
允许通知
营销邮件
社交媒体更新
);
}
```
### 横向分组
```tsx
import {Switch, SwitchGroup} from "@heroui/react";
export function GroupHorizontal() {
return (
通知
营销
社交
);
}
```
### 表单集成
```tsx
"use client";
import {Button, Switch, SwitchGroup} from "@heroui/react";
import React from "react";
export function Form() {
const handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
alert(
`表单提交内容:\n${Array.from(formData.entries())
.map(([key, value]) => `${key}: ${value}`)
.join("\n")}`,
);
};
return (
启用通知
订阅新闻简报
接收营销更新
Submit
);
}
```
### 渲染属性
```tsx
"use client";
import {Switch} from "@heroui/react";
export function RenderProps() {
return (
{({isSelected}) => (
{isSelected ? "已开启" : "已关闭"}
)}
);
}
```
### 渲染函数
```tsx
"use client";
import {Switch} from "@heroui/react";
export function RenderFunction() {
return (
}>
启用通知
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Description, Label, Switch} from "@heroui/react";
export function CustomStyles() {
return (
自动保存草稿
输入时会自动保存更改。
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.switch {
@apply inline-flex gap-3 items-center;
}
.switch__control {
@apply h-5 w-8 bg-gray-400 data-[selected=true]:bg-blue-500;
}
.switch__thumb {
@apply bg-white shadow-sm;
}
.switch__content {
@apply flex flex-col gap-1;
}
.switch__icon {
@apply h-3 w-3 text-current;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
#### Switch 类 \[!toc]
Switch 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/switch.css)):
* `.switch` - Switch 根容器(字段)
* `.switch__content` - 包裹控件与标签文本的可点击 label
* `.switch__control` - Switch 轨道
* `.switch__thumb` - 可移动的滑块
* `.switch__icon` - 滑块内可选图标
* `.switch--sm` - 小尺寸变体
* `.switch--md` - 中尺寸变体(默认)
* `.switch--lg` - 大尺寸变体
#### SwitchGroup 类 \[!toc]
SwitchGroup 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/switch-group.css)):
* `.switch-group` - Switch 组容器
* `.switch-group__items` - Switch 项容器
* `.switch-group--horizontal` - 横向布局
* `.switch-group--vertical` - 纵向布局(默认)
### 交互状态
该 Switch 同时支持 CSS 伪类与 data 属性,以提供更灵活的状态控制:
* **已选中**:`[data-selected="true"]`(滑块位置与背景色变化)
* **悬停**:`:hover` 或 `[data-hovered="true"]`(作用于 `Switch.Control` / 按钮)
* **聚焦**:`:focus-visible` 或 `[data-focus-visible="true"]`(在按钮上显示轨道焦点环)
* **禁用**:`[data-disabled="true"]`(降低透明度,包括帮助文本)
* **按压**:`:active` 或 `[data-pressed="true"]`
## API 参考
### Switch
继承自 [React Aria SwitchField](https://react-spectrum.adobe.com/react-aria/Switch.html)。
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ------------------------------------------------------------------------------ | ---------- | ---------------------- |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Switch 尺寸。 |
| `isSelected` | `boolean` | `false` | Switch 是否打开。 |
| `defaultSelected` | `boolean` | `false` | 默认是否打开(非受控)。 |
| `isDisabled` | `boolean` | `false` | Switch 是否禁用。 |
| `isInvalid` | `boolean` | `false` | Switch 是否无效。 |
| `isReadOnly` | `boolean` | `false` | Switch 是否只读。 |
| `isRequired` | `boolean` | `false` | Switch 是否必须打开。 |
| `validate` | `(value: boolean) => ValidationError \| true \| null \| undefined` | - | 自定义校验函数。 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 校验或 ARIA 校验。 |
| `name` | `string` | - | 输入元素名称,用于提交 HTML 表单。 |
| `value` | `string` | - | 输入元素值,用于提交 HTML 表单。 |
| `onChange` | `(isSelected: boolean) => void` | - | Switch 值变化时的事件处理函数。 |
| `onPress` | `(e: PressEvent) => void` | - | Switch 被按下时的事件处理函数。 |
| `children` | `React.ReactNode \| (values: SwitchFieldRenderProps) => React.ReactNode` | - | Switch 内容或字段级渲染 prop。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Switch.Content
包裹控件与标签文本的可点击 ``。请把 `Switch.Control` 与 `Label` 放在它内部;`Description`/`FieldError` 作为 `Switch.Content` 的兄弟节点。对于没有标签的 switch,省略 `Label` 并在 `Switch` 上传入 `aria-label`。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------- | --- | ------------------------- |
| `children` | `React.ReactNode \| (values: SwitchButtonRenderProps) => React.ReactNode` | - | 按钮内容(控件 + 标签),或按钮级渲染 prop |
| `className` | `string \| (values: SwitchButtonRenderProps) => string` | - | 应用到可点击 label 的类名 |
### SwitchFieldRenderProps
在根 `Switch` 上使用渲染 prop 时,提供以下字段级值:
| Prop | 类型 | 描述 |
| ------------ | ------------- | -------------- |
| `isSelected` | `boolean` | Switch 当前是否打开。 |
| `isDisabled` | `boolean` | Switch 是否禁用。 |
| `isReadOnly` | `boolean` | Switch 是否只读。 |
| `isInvalid` | `boolean` | Switch 是否无效。 |
| `isRequired` | `boolean` | Switch 是否必填。 |
| `state` | `ToggleState` | Switch 的状态。 |
### SwitchButtonRenderProps
`Switch.Control` 使用按钮级渲染 prop(`isHovered`、`isPressed`、`isFocusVisible` 等)。将函数作为 `Switch.Control` 的子元素即可访问。
### SwitchGroup
| Prop | 类型 | 默认值 | 描述 |
| ------------- | ---------------------------- | ------------ | -------------- |
| `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | Switch 组方向。 |
| `children` | `React.ReactNode` | - | 要渲染的 Switch 项。 |
| `className` | `string` | - | 额外的 CSS 类。 |
## 相关组件
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **Button**: Allows a user to perform an action
# Badge 徽章
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/badge
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(data-display)/badge.mdx
> 相对其他元素定位的小型指示器,常用于通知数量、状态点与标签
## 用法
```tsx
import { Badge } from '@heroui/react';
```
```tsx
import {Avatar, Badge} from "@heroui/react";
const GREEN_AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg";
const ORANGE_AVATAR_URL =
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg";
const BLUE_AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg";
export function BadgeBasic() {
return (
);
}
```
## 组件结构
Badge 通过 `Badge.Anchor` 相对其他元素定位。纯文本子节点会自动包裹在 `` 中。
> 独立标签用法请使用 [Chip](/docs/react/components/chip) 组件。
```tsx
5
```
## 示例
### 变体
```tsx
import {Avatar, Badge, Separator} from "@heroui/react";
import React from "react";
const AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg";
const variants = ["primary", "secondary", "soft"] as const;
const VARIANT_LABELS: Record<(typeof variants)[number], string> = {
primary: "主色",
secondary: "次色",
soft: "柔和",
};
const colors = ["accent", "default", "success", "warning", "danger"] as const;
export function BadgeVariants() {
return (
{variants.map((variant, index) => (
{VARIANT_LABELS[variant]}
{colors.map((color) => (
JD
5
))}
{index < variants.length - 1 && }
))}
);
}
```
### 尺寸
```tsx
import {Avatar, Badge} from "@heroui/react";
const AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg";
export function BadgeSizes() {
const sizes = ["sm", "md", "lg"] as const;
return (
{sizes.map((size) => (
JD
5
))}
);
}
```
### 颜色
```tsx
import {Avatar, Badge} from "@heroui/react";
const AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg";
export function BadgeColors() {
const colors = ["default", "accent", "success", "warning", "danger"] as const;
return (
{colors.map((color) => (
JD
))}
);
}
```
### 徽章位置
```tsx
import {Avatar, Badge} from "@heroui/react";
const AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg";
const placements = ["top-right", "top-left", "bottom-right", "bottom-left"] as const;
const PLACEMENT_LABELS: Record<(typeof placements)[number], string> = {
"bottom-left": "左下",
"bottom-right": "右下",
"top-left": "左上",
"top-right": "右上",
};
export function BadgePlacements() {
return (
{placements.map((placement) => (
JD
{PLACEMENT_LABELS[placement]}
))}
);
}
```
### 点状徽标
空徽章作为状态指示器,适用于在线/离线或活动信号。
```tsx
import {Avatar, Badge} from "@heroui/react";
const AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg";
export function BadgeDot() {
const colors = ["accent", "success", "warning", "danger"] as const;
return (
{colors.map((color) => (
JD
))}
);
}
```
### 带内容
Badge 支持文本、数字与图标。无子节点时渲染为点状指示器。
```tsx
import {Bell} from "@gravity-ui/icons";
import {Avatar, Badge} from "@heroui/react";
const AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg";
export function BadgeWithContent() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Avatar, Badge} from "@heroui/react";
export function CustomStyles() {
return (
KW
5
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Badge 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.badge {
@apply rounded-full text-xs;
}
.badge__label {
@apply font-semibold;
}
.badge--accent {
@apply shadow-sm;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Badge 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/badge.css)):
#### 基础类 \[!toc]
* `.badge` - 基础徽章容器样式
* `.badge__label` - 标签文本 slot 样式
* `.badge-anchor` - 锚定元素的定位包装器
#### 颜色类 \[!toc]
* `.badge--accent` - 强调色变体
* `.badge--danger` - 危险色变体
* `.badge--default` - 默认色变体
* `.badge--success` - 成功色变体
* `.badge--warning` - 警告色变体
#### 变体类 \[!toc]
* `.badge--primary` - 填充背景的主变体
* `.badge--secondary` - 默认背景的次变体
* `.badge--soft` - 浅色背景的 soft 变体
#### 尺寸类 \[!toc]
* `.badge--sm` - 小尺寸
* `.badge--md` - 中尺寸(默认)
* `.badge--lg` - 大尺寸
#### 位置类 \[!toc]
* `.badge--top-right` - 右上角(默认)
* `.badge--top-left` - 左上角
* `.badge--bottom-right` - 右下角
* `.badge--bottom-left` - 左下角
#### 复合变体类 \[!toc]
Badge 支持组合变体与颜色类(如 `.badge--primary.badge--accent`)。以下组合有默认样式:
**Primary Variants:**
* `.badge--primary.badge--accent` - 填充背景的 primary accent
* `.badge--primary.badge--default` - 填充背景的 primary default
* `.badge--primary.badge--success` - 填充背景的 primary success
* `.badge--primary.badge--warning` - 填充背景的 primary warning
* `.badge--primary.badge--danger` - 填充背景的 primary danger
**Soft Variants:**
* `.badge--soft.badge--accent` - 浅色背景的 soft accent
* `.badge--soft.badge--default` - 浅色背景的 soft default
* `.badge--soft.badge--success` - 浅色背景的 soft success
* `.badge--soft.badge--warning` - 浅色背景的 soft warning
* `.badge--soft.badge--danger` - 浅色背景的 soft danger
## API 参考
### Badge
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------------------------------------------- | ------------- | ---------------------- |
| `children` | `React.ReactNode` | - | 徽章内容(文本、数字或图标)。省略时渲染为点 |
| `className` | `string` | - | 根元素附加 CSS 类 |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | 徽章颜色变体 |
| `variant` | `"primary" \| "secondary" \| "soft"` | `"primary"` | 视觉样式变体 |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 徽章尺寸 |
| `placement` | `"top-right" \| "top-left" \| "bottom-right" \| "bottom-left"` | `"top-right"` | 相对锚点的位置 |
### Badge.Anchor
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------------- |
| `children` | `React.ReactNode` | - | 锚定元素及 Badge 本身 |
| `className` | `string` | - | 锚点包装器附加 CSS 类 |
### Badge.Label
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ---------------- |
| `children` | `React.ReactNode` | - | 标签文本内容 |
| `className` | `string` | - | 标签 slot 附加 CSS 类 |
## 相关组件
## Related Components
* **Avatar**: Display user profile images
* **Chip**: Compact elements for tags and filters
# Chip 标签
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/chip
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(data-display)/chip.mdx
> 用于显示标签、状态与分类的小型信息徽章
## 用法
```tsx
import { Chip } from '@heroui/react';
```
```tsx
import {Chip} from "@heroui/react";
export function ChipBasic() {
return (
默认
强调
成功
警告
危险
);
}
```
## 组件结构
> 纯文本子节点会自动包裹在 `` 中。
```tsx
Label text
```
## 示例
### 变体
```tsx
import {CircleDashed} from "@gravity-ui/icons";
import {Chip, Separator} from "@heroui/react";
import React from "react";
const sizes = ["lg", "md", "sm"] as const;
const SIZE_LABELS: Record<(typeof sizes)[number], string> = {
lg: "大",
md: "中",
sm: "小",
};
const variants = ["primary", "secondary", "tertiary", "soft"] as const;
const VARIANT_LABELS: Record<(typeof variants)[number], string> = {
primary: "主要",
secondary: "次要",
soft: "柔和",
tertiary: "第三",
};
const colors = ["accent", "default", "success", "warning", "danger"] as const;
const COLOR_LABELS: Record<(typeof colors)[number], string> = {
accent: "强调",
danger: "危险",
default: "默认",
success: "成功",
warning: "警告",
};
export function ChipVariants() {
return (
{sizes.map((size, index) => (
{SIZE_LABELS[size]}
{colors.map((color) => (
{COLOR_LABELS[color]}
))}
{variants.map((variant) => (
{VARIANT_LABELS[variant]}
{colors.map((color) => (
标签
))}
))}
{index < sizes.length - 1 && }
))}
);
}
```
### 状态类型
```tsx
import {Ban, Check, CircleFill, CircleInfo, TriangleExclamation} from "@gravity-ui/icons";
import {Chip} from "@heroui/react";
export function ChipStatuses() {
return (
默认
活跃
待处理
未激活
新功能
可用
测试版
已弃用
);
}
```
### 带图标
```tsx
import {ChevronDown, CircleCheckFill, CircleFill, Clock, Xmark} from "@gravity-ui/icons";
import {Chip} from "@heroui/react";
export function ChipWithIcon() {
return (
信息
已完成
待处理
失败
标签
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Chip} from "@heroui/react";
export function CustomStyles() {
return (
草稿
审核中
已发布
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Chip 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.chip {
@apply rounded-full text-xs;
}
.chip__label {
@apply font-medium;
}
.chip--accent {
@apply border-accent/20;
}
.chip--accent .chip__label {
@apply text-accent;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Chip 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/chip.css)):
#### 基础类 \[!toc]
* `.chip` - 基础标签容器样式
* `.chip__label` - 标签文本 slot 样式
#### 颜色类 \[!toc]
* `.chip--accent` - 强调色变体
* `.chip--danger` - 危险色变体
* `.chip--default` - 默认色变体
* `.chip--success` - 成功色变体
* `.chip--warning` - 警告色变体
#### 变体类 \[!toc]
* `.chip--primary` - 填充背景的主变体
* `.chip--secondary` - 带边框的次变体
* `.chip--tertiary` - 透明背景的三级变体
* `.chip--soft` - 浅色背景的 soft 变体
#### 尺寸类 \[!toc]
* `.chip--sm` - 小尺寸
* `.chip--md` - 中尺寸(默认)
* `.chip--lg` - 大尺寸
#### 复合变体类 \[!toc]
Chip 支持组合变体与颜色类(如 `.chip--secondary.chip--accent`)。以下组合有默认样式:
**Primary Variants:**
* `.chip--primary.chip--accent` - 填充背景的 primary accent 组合
* `.chip--primary.chip--success` - 填充背景的 primary success 组合
* `.chip--primary.chip--warning` - 填充背景的 primary warning 组合
* `.chip--primary.chip--danger` - 填充背景的 primary danger 组合
**Soft Variants:**
* `.chip--accent.chip--soft` - 浅色背景的 soft accent 组合
* `.chip--success.chip--soft` - 浅色背景的 soft success 组合
* `.chip--warning.chip--soft` - 浅色背景的 soft warning 组合
* `.chip--danger.chip--soft` - 浅色背景的 soft danger 组合
**Note:** 可使用 CSS 中的 `@layer components` 指令为任意变体-颜色组合(如 `.chip--secondary.chip--accent`、`.chip--tertiary.chip--success`)应用自定义样式。
## API 参考
### Chip
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | ------------- | ----------- |
| `children` | `React.ReactNode` | - | 标签内显示的内容 |
| `className` | `string` | - | 根元素附加 CSS 类 |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | 标签颜色变体 |
| `variant` | `"primary" \| "secondary" \| "tertiary" \| "soft"` | `"secondary"` | 视觉样式变体 |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 标签尺寸 |
### Chip.Label
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ---------------- |
| `children` | `React.ReactNode` | - | 标签文本内容 |
| `className` | `string` | - | 标签 slot 附加 CSS 类 |
## 相关组件
## Related Components
* **Avatar**: Display user profile images
* **CloseButton**: Button for dismissing overlays
* **Separator**: Visual divider between content
# Table 表格
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/table
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(data-display)/table.mdx
> 表格以行和列展示结构化数据,支持排序、选择、列宽调整与无限滚动。
## 用法
```tsx
import { Table } from '@heroui/react';
```
```tsx
import {Table} from "@heroui/react";
export function Basic() {
return (
姓名
角色
状态
邮箱
Kate Moore
首席执行官
在职
kate@acme.com
John Smith
首席技术官
在职
john@acme.com
Sara Johnson
首席营销官
休假
sara@acme.com
Michael Brown
首席财务官
在职
michael@acme.com
);
}
```
## 组件结构
```tsx
import { Table } from '@heroui/react';
export default () => (
{({ sortDirection }) => (
Name
)}
Role
Kate Moore
CEO
{/* Optional footer content */}
);
```
## 示例
### 次要变体
```tsx
import {Table} from "@heroui/react";
export function SecondaryVariant() {
return (
姓名
角色
状态
邮箱
Kate Moore
首席执行官
在职
kate@acme.com
John Smith
首席技术官
在职
john@acme.com
Sara Johnson
首席营销官
休假
sara@acme.com
Michael Brown
首席财务官
在职
michael@acme.com
);
}
```
### 异步加载
使用 `Table.LoadMore` 实现无限滚动。它会渲染一个哨兵行,滚动到视口时触发 `onLoadMore`。
```tsx
"use client";
import {Chip, Spinner, Table} from "@heroui/react";
import {useCallback, useRef, useState} from "react";
interface User {
id: number;
name: string;
role: string;
status: string;
email: string;
}
const statusColorMap: Record = {
休假: "warning",
在职: "success",
未激活: "danger",
};
const allUsers: User[] = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "首席执行官", status: "在职"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "首席技术官", status: "在职"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "首席营销官", status: "休假"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "首席财务官", status: "在职"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "产品经理",
status: "未激活",
},
{email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "首席设计师", status: "在职"},
{
email: "olivia@acme.com",
id: 7,
name: "Olivia Martinez",
role: "前端工程师",
status: "在职",
},
{
email: "james@acme.com",
id: 8,
name: "James Taylor",
role: "后端工程师",
status: "在职",
},
{
email: "sophia@acme.com",
id: 9,
name: "Sophia Anderson",
role: "测试工程师",
status: "休假",
},
{email: "liam@acme.com", id: 10, name: "Liam Thomas", role: "DevOps 工程师", status: "在职"},
{
email: "lucas@acme.com",
id: 11,
name: "Lucas Martinez",
role: "产品经理",
status: "在职",
},
{
email: "emma@acme.com",
id: 12,
name: "Emma Johnson",
role: "前端工程师",
status: "在职",
},
{email: "noah@acme.com", id: 13, name: "Noah Davis", role: "后端工程师", status: "在职"},
{email: "ava@acme.com", id: 14, name: "Ava Wilson", role: "首席设计师", status: "在职"},
{
email: "oliver@acme.com",
id: 15,
name: "Oliver Martinez",
role: "前端工程师",
status: "在职",
},
{
email: "isabella@acme.com",
id: 16,
name: "Isabella Johnson",
role: "后端工程师",
status: "在职",
},
{email: "mia@acme.com", id: 17, name: "Mia Davis", role: "首席设计师", status: "在职"},
{
email: "william@acme.com",
id: 18,
name: "William Wilson",
role: "前端工程师",
status: "在职",
},
];
const ITEMS_PER_PAGE = 6;
const columns = [
{id: "name", name: "姓名"},
{id: "role", name: "角色"},
{id: "status", name: "状态"},
{id: "email", name: "邮箱"},
];
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 && (
)}
);
}
```
### 排序
在 `Table.Column` 上使用 `allowsSorting` 属性可启用列排序。在 `Table.Content` 上使用 `sortDescriptor` 与 `onSortChange` 管理排序状态。将标签包裹在 `Table.SortableColumnHeader` 中,并将列 render prop 的 `sortDirection` 转发以渲染默认升序/降序指示器。
```tsx
"use client";
import type {SortDescriptor} from "@heroui/react";
import {Table} from "@heroui/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: "首席执行官", status: "在职"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "首席技术官", status: "在职"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "首席营销官", status: "休假"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "首席财务官", status: "在职"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "产品经理",
status: "未激活",
},
];
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}) => (
姓名
)}
{({sortDirection}) => (
角色
)}
{({sortDirection}) => (
状态
)}
{({sortDirection}) => (
邮箱
)}
{sortedUsers.map((user) => (
{user.name}
{user.role}
{user.status}
{user.email}
))}
);
}
```
### 选择
在 `Table.Content` 上使用 `selectionMode` 启用行选择。使用 `slot="selection"` 的 `Checkbox` 实现全选与逐行勾选。
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Checkbox, Table} from "@heroui/react";
import {useState} from "react";
const users = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "首席执行官", status: "在职"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "首席技术官", status: "在职"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "首席营销官", status: "休假"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "首席财务官", status: "在职"},
];
export function SelectionDemo() {
const [selectedKeys, setSelectedKeys] = useState(new Set());
return (
姓名
角色
状态
邮箱
{users.map((user) => (
{user.name}
{user.role}
{user.status}
{user.email}
))}
已选:{" "}
{selectedKeys === "all"
? "全部"
: selectedKeys.size > 0
? Array.from(selectedKeys).join(", ")
: "无"}
);
}
```
### 可展开行
行可嵌套以展示层级数据。使用 `treeColumn` 指定列,并在该列单元格中渲染 `slot="chevron"` 的 `Button`,供用户展开/收起行。使用 `expandedKeys` 控制哪些行处于展开状态。
```tsx
"use client";
import type {Selection} from "@heroui/react";
import {Button, Table, cn} from "@heroui/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: "周报", type: "文件"},
{children: [], date: "8/20/2025", id: "4", title: "预算", type: "文件"},
],
date: "8/2/2025",
id: "2",
title: "项目",
type: "文件夹",
},
],
date: "10/20/2025",
id: "1",
title: "文档",
type: "文件夹",
},
{
children: [
{children: [], date: "1/23/2026", id: "6", title: "图片 1", type: "文件"},
{children: [], date: "2/3/2026", id: "7", title: "图片 2", type: "文件"},
],
date: "2/3/2026",
id: "5",
title: "照片",
type: "文件夹",
},
];
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 (
姓名
类型
修改日期
{renderExpandableRow}
);
}
```
### 分页
使用 `Table.Footer` 在表格下方添加分页组件。
```tsx
"use client";
import {Pagination, Table} from "@heroui/react";
import {useMemo, useState} from "react";
const columns = [
{id: "name", name: "姓名"},
{id: "role", name: "角色"},
{id: "status", name: "状态"},
{id: "email", name: "邮箱"},
];
const users = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "首席执行官", status: "在职"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "首席技术官", status: "在职"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "首席营销官", status: "休假"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "首席财务官", status: "在职"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "产品经理",
status: "未激活",
},
{email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "首席设计师", status: "在职"},
{
email: "olivia@acme.com",
id: 7,
name: "Olivia Martinez",
role: "前端工程师",
status: "在职",
},
{
email: "james@acme.com",
id: 8,
name: "James Taylor",
role: "后端工程师",
status: "在职",
},
];
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}–{end} / 共 {users.length} 条
setPage((p) => Math.max(1, p - 1))}
>
上一页
{pages.map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => Math.min(totalPages, p + 1))}
>
下一页
);
}
```
### 列宽调整
将表格包裹在 `Table.ResizableContainer` 中,并在每个可调整宽度的列内添加 `Table.ColumnResizer`。
```tsx
import {Chip, Table} from "@heroui/react";
export function ColumnResizing() {
return (
姓名
角色
状态
邮箱
Kate Moore
首席执行官
Active
kate@acme.com
John Smith
首席技术官
Active
john@acme.com
Sara Johnson
首席营销官
On Leave
sara@acme.com
Michael Brown
首席财务官
Active
michael@acme.com
Emily Davis
产品经理
Inactive
emily@acme.com
);
}
```
### 空状态
在 `Table.Body` 上使用 `renderEmptyState`,在表格无数据时展示自定义消息。
```tsx
"use client";
import {EmptyState, Table} from "@heroui/react";
import {Icon} from "@iconify/react";
export function EmptyStateDemo() {
return (
姓名
角色
状态
邮箱
(
未找到结果
)}
>
{[]}
);
}
```
### 虚拟滚动
Table 通过 [Virtualizer](https://react-aria.adobe.com/Virtualizer) 支持虚拟化,仅渲染视口内可见行,从而高效展示大数据集。
```tsx
"use client";
import {Table, TableLayout, Virtualizer} from "@heroui/react";
interface User {
id: number;
name: string;
role: string;
email: string;
}
export function Virtualization() {
const roles = [
"软件工程师",
"高级工程师",
"资深工程师",
"产品经理",
"设计师",
"数据分析师",
"测试工程师",
"DevOps 工程师",
"营销经理",
"销售代表",
];
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 (
姓名
角色
邮箱
{(user) => (
{user.name}
{user.role}
{user.email}
)}
);
}
```
### TanStack Table
HeroUI 的 Table 作为无头表格库之上的渲染层。
本示例使用 [TanStack Table](https://tanstack.com/table) 处理列定义、排序与分页,而 HeroUI 负责样式与无障碍。
```tsx
"use client";
import type {SortDescriptor} from "@heroui/react";
import type {SortingState} from "@tanstack/react-table";
import {Chip, Pagination, Table} from "@heroui/react";
import {
createColumnHelper,
createPaginatedRowModel,
createSortedRowModel,
flexRender,
rowPaginationFeature,
rowSortingFeature,
sortFn_alphanumeric,
tableFeatures,
useTable,
} from "@tanstack/react-table";
import {useMemo, useState} from "react";
// --- Data -----------------------------------------------------------------
interface User {
id: number;
name: string;
role: string;
status: "在职" | "未激活" | "休假";
email: string;
}
const statusColorMap: Record = {
休假: "warning",
在职: "success",
未激活: "danger",
};
const users: User[] = [
{email: "kate@acme.com", id: 1, name: "Kate Moore", role: "首席执行官", status: "在职"},
{email: "john@acme.com", id: 2, name: "John Smith", role: "首席技术官", status: "在职"},
{email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "首席营销官", status: "休假"},
{email: "michael@acme.com", id: 4, name: "Michael Brown", role: "首席财务官", status: "在职"},
{
email: "emily@acme.com",
id: 5,
name: "Emily Davis",
role: "产品经理",
status: "未激活",
},
{email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "首席设计师", status: "在职"},
{
email: "olivia@acme.com",
id: 7,
name: "Olivia Martinez",
role: "前端工程师",
status: "在职",
},
{
email: "james@acme.com",
id: 8,
name: "James Taylor",
role: "后端工程师",
status: "在职",
},
];
// --- TanStack Features & Column Definitions -------------------------------
const features = tableFeatures({
paginatedRowModel: createPaginatedRowModel(),
rowPaginationFeature,
rowSortingFeature,
sortFns: {
alphanumeric: sortFn_alphanumeric,
},
sortedRowModel: createSortedRowModel(),
});
const columnHelper = createColumnHelper();
const columns = columnHelper.columns([
columnHelper.accessor("name", {header: "姓名"}),
columnHelper.accessor("role", {header: "角色"}),
columnHelper.accessor("status", {
cell: (info) => (
{info.getValue()}
),
header: "状态",
}),
columnHelper.accessor("email", {header: "邮箱"}),
]);
// --- 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}];
}
// --- Component ------------------------------------------------------------
const PAGE_SIZE = 4;
export function TanstackTable() {
const [sorting, setSorting] = useState([]);
const table = useTable({
columns,
data: users,
features,
initialState: {pagination: {pageIndex: 0, pageSize: PAGE_SIZE}},
onSortingChange: setSorting,
state: {sorting},
});
const sortDescriptor = useMemo(() => toSortDescriptor(sorting), [sorting]);
const {pageIndex} = table.state.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.getAllCells().map((cell) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
))}
{start}–{end} / 共 {users.length} 条
table.previousPage()}
>
上一页
{pages.map((p) => (
table.setPageIndex(p - 1)}
>
{p}
))}
table.nextPage()}
>
下一页
);
}
```
### 自定义单元格
```tsx
"use client";
import type {Selection, SortDescriptor} from "@heroui/react";
import {Avatar, Button, Checkbox, Chip, Table} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useMemo, useState} from "react";
interface User {
id: number;
name: string;
image_url: string;
role: string;
status: "在职" | "未激活" | "休假";
email: string;
}
const statusColorMap: Record = {
休假: "warning",
在职: "success",
未激活: "danger",
};
const users: User[] = [
{
email: "kate@acme.com",
id: 4586932,
image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "Kate Moore",
role: "首席执行官",
status: "在职",
},
{
email: "john@acme.com",
id: 5273849,
image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "John Smith",
role: "首席技术官",
status: "在职",
},
{
email: "sara@acme.com",
id: 7492836,
image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "Sara Johnson",
role: "首席营销官",
status: "休假",
},
{
email: "michael@acme.com",
id: 8293746,
image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "Michael Brown",
role: "首席财务官",
status: "在职",
},
{
email: "emily@acme.com",
id: 1234567,
image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "Emily Davis",
role: "产品经理",
status: "未激活",
},
];
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}) => (
员工 ID
)}
{({sortDirection}) => (
成员
)}
{({sortDirection}) => (
角色
)}
{({sortDirection}) => (
状态
)}
操作
{sortedUsers.map((user) => (
#{user.id.toString()}{" "}
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
{user.name}
{user.email}
{user.role}
{user.status}
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Table} from "@heroui/react";
const headerCell = "text-xs font-semibold uppercase tracking-wide text-muted";
const rowClass =
"border-t border-border/60 transition-colors hover:bg-default even:bg-surface-secondary";
export function CustomStyles() {
return (
姓名
角色
状态
Kate Moore
CEO
在职
John Smith
CTO
在职
Sara Johnson
CMO
休假中
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Table 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/table.css)):
#### 基础类 \[!toc]
* `.table-root` - 根容器(命名为 `table-root` 而非 `table`,因为 `table` 是 Tailwind CSS 内置的 `display: table` 工具类)
* `.table__scroll-container` - 横向滚动包裹层与自定义滚动条
* `.table__content` - `` 元素
* `.table__header` - 表头行(``)
* `.table__column` - 列表头单元格(``)
* `.table__body` - 表体(` `)
* `.table__row` - 行(``)
* `.table__cell` - 数据单元格(``)
* `.table__footer` - 表底容器(位于 table 外部)
#### 进阶类 \[!toc]
* `.table__column-resizer` - 列宽拖拽手柄
* `.table__resizable-container` - 启用列宽调整的包裹层
* `.table__load-more` - 无限滚动的哨兵行
* `.table__load-more-content` - 加载指示器的样式容器
* `.table__sortable-column-header` - 可排序列标签与指示器的包裹层
* `.table__sortable-column-indicator` - 排序方向 chevron(通过 `[data-direction="descending"]` 翻转)
#### 变体类 \[!toc]
* `.table-root--primary` - 灰色背景容器与卡片式表体(默认)
* `.table-root--secondary` - 无背景,独立圆角表头
### 交互状态
Table 同时支持 CSS 伪类与 data 属性,以提供更灵活的状态控制:
* **悬停**:`:hover` 或 `[data-hovered="true"]`(行背景变化)
* **已选中**:`[data-selected="true"]`(行高亮)
* **聚焦**:`:focus-visible` 或 `[data-focus-visible="true"]`(行、列与单元格的内嵌焦点环)
* **禁用**:`:disabled` 或 `[aria-disabled="true"]`(降低透明度)
* **可排序**:`[data-allows-sorting="true"]`(列上的交互指针样式)
* **拖动中**:`[data-dragging="true"]`(降低透明度)
* **放置目标**:`[data-drop-target="true"]`(强调色背景)
## API 参考
### Table
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------- | ----------- | -------------------------------------- |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。Primary 为灰色背景容器;Secondary 为扁平透明行。 |
| `className` | `string` | - | 根容器的额外 CSS 类。 |
| `children` | `React.ReactNode` | - | 表格内容(ScrollContainer、Footer 等)。 |
### Table.ScrollContainer
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ------------------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `React.ReactNode` | - | `Table.Content` 元素。 |
### Table.Content
继承自 [React Aria Table](https://react-spectrum.adobe.com/react-aria/Table.html)。
| Prop | 类型 | 默认值 | 描述 |
| ------------------- | -------------------------------------- | -------- | ------------- |
| `aria-label` | `string` | - | 表格的无障碍标签。 |
| `selectionMode` | `"none" \| "single" \| "multiple"` | `"none"` | 选择行为。 |
| `selectedKeys` | `Selection` | - | 受控的已选中 key。 |
| `onSelectionChange` | `(keys: Selection) => void` | - | 选择变化时的事件处理函数。 |
| `sortDescriptor` | `SortDescriptor` | - | 当前排序状态。 |
| `onSortChange` | `(descriptor: SortDescriptor) => void` | - | 排序变化时的事件处理函数。 |
| `className` | `string` | - | 额外的 CSS 类。 |
### Table.Header
继承自 [React Aria TableHeader](https://react-spectrum.adobe.com/react-aria/Table.html#tableheader)。
| Prop | 类型 | 默认值 | 描述 |
| ---------- | --------------------------------------------------- | --- | -------------- |
| `columns` | `T[]` | - | 渲染函数模式下的动态列数据。 |
| `children` | `React.ReactNode \| (column: T) => React.ReactNode` | - | 静态列或渲染函数。 |
### Table.Column
继承自 [React Aria Column](https://react-spectrum.adobe.com/react-aria/Table.html#column)。
| Prop | 类型 | 默认值 | 描述 |
| --------------- | ------------------------------------------------------------------- | ------- | --------------- |
| `id` | `string` | - | 列标识符。 |
| `allowsSorting` | `boolean` | `false` | 列是否可排序。 |
| `isRowHeader` | `boolean` | `false` | 该列是否作为行表头。 |
| `defaultWidth` | `string \| number` | - | 可调整列的默认宽度。 |
| `minWidth` | `number` | - | 可调整列的最小宽度。 |
| `children` | `React.ReactNode \| (values: ColumnRenderProps) => React.ReactNode` | - | 列内容或带排序方向的渲染函数。 |
### Table.Body
继承自 [React Aria TableBody](https://react-spectrum.adobe.com/react-aria/Table.html#tablebody)。
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | ------------------------------------------------- | --- | -------------- |
| `items` | `T[]` | - | 渲染函数模式下的动态行数据。 |
| `renderEmptyState` | `() => React.ReactNode` | - | 表格为空时展示的内容。 |
| `children` | `React.ReactNode \| (item: T) => React.ReactNode` | - | 静态行或渲染函数。 |
### Table.Row
继承自 [React Aria Row](https://react-spectrum.adobe.com/react-aria/Table.html#row)。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------ | --- | ---------- |
| `id` | `string \| number` | - | 行标识符。 |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `React.ReactNode` | - | 行单元格。 |
### Table.Cell
继承自 [React Aria Cell](https://react-spectrum.adobe.com/react-aria/Table.html#cell)。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ---------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `React.ReactNode` | - | 单元格内容。 |
### Table.SortableColumnHeader
渲染可排序列的标签与升序 / 降序指示器。请在 `Table.Column` 的渲染函数回调中使用,并将 `sortDirection` 透传进来。
| Prop | 类型 | 默认值 | 描述 |
| --------------- | ----------------------------- | ------ | ---------------------------------------------------- |
| `sortDirection` | `"ascending" \| "descending"` | - | 当前排序方向。请从 `Table.Column` 的渲染函数中透传。 |
| `showIndicator` | `boolean` | `true` | 当存在排序方向时是否渲染指示器图标。 |
| `indicator` | `React.ReactNode` | - | 自定义指示器元素。会覆盖默认的 chevron,并会被自动注入 `data-direction` 属性。 |
| `className` | `string` | - | 包裹元素的额外 CSS 类。 |
| `children` | `React.ReactNode` | - | 列标签内容。 |
### Table.Footer
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ----------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `React.ReactNode` | - | 表底内容(例如分页)。 |
### Table.ColumnResizer
继承自 [React Aria ColumnResizer](https://react-spectrum.adobe.com/react-aria/Table.html#columnresizer)。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | ---------- |
| `className` | `string` | - | 额外的 CSS 类。 |
### Table.ResizableContainer
继承自 [React Aria ResizableTableContainer](https://react-spectrum.adobe.com/react-aria/Table.html#resizabletablecontainer)。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ------------------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `React.ReactNode` | - | `Table.Content` 元素。 |
### Table.LoadMore
继承自 [React Aria TableLoadMoreItem](https://react-spectrum.adobe.com/react-aria/Table.html)。
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ----------------- | ------- | -------------- |
| `isLoading` | `boolean` | `false` | 数据是否正在加载。 |
| `onLoadMore` | `() => void` | - | 哨兵行可见时的事件处理函数。 |
| `children` | `React.ReactNode` | - | 加载指示器内容。 |
### Table.LoadMoreContent
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------------------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `React.ReactNode` | - | 加载指示器内容(例如 Spinner)。 |
### Table.Collection
由 React Aria `Collection` 重新导出。用于在行内与静态单元格并存时渲染动态单元格(例如复选框)。
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ------------------------------ | --- | ---------- |
| `items` | `T[]` | - | 集合条目。 |
| `children` | `(item: T) => React.ReactNode` | - | 每个条目的渲染函数。 |
### TableLayout
| Name | 类型 | 默认值 | 描述 |
| ------------------------ | --------------------- | --- | --------------------------------------------- |
| `rowHeight` | `number \| undefined` | 48 | 行的固定高度(px)。 |
| `estimatedRowHeight` | `number \| undefined` | — | 行高可变时的估算高度。 |
| `headingHeight` | `number \| undefined` | 48 | 分区表头的固定高度(px)。 |
| `estimatedHeadingHeight` | `number \| undefined` | — | 表头高度可变时的估算高度。 |
| `loaderHeight` | `number \| undefined` | 48 | 加载器元素的固定高度(px)。该加载器用于在根级或嵌套行/分区中渲染「加载更多」等加载行。 |
| `dropIndicatorThickness` | `number \| undefined` | 2 | 放置指示器的线条粗细。 |
| `gap` | `number \| undefined` | 0 | 条目之间的间距。 |
| `padding` | `number \| undefined` | 0 | 列表的内边距。 |
## 相关组件
## Related Components
* **Pagination**: Page navigation with composable page links and controls
* **Checkbox**: Binary choice input control
* **Chip**: Compact elements for tags and filters
# Calendar 日历
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/calendar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(date-and-time)/calendar.mdx
> 基于 React Aria Calendar 的可组合日期选择器,支持月网格、导航与年份选择
## 用法
```tsx
import { Calendar } from '@heroui/react';
```
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function Basic() {
return (
{(day) => {day} }
{(date) => }
);
}
```
## 组件结构
```tsx
import {Calendar} from '@heroui/react';
export default () => (
{(day) => {day} }
{(date) => }
)
```
## 示例
### 禁用
```tsx
"use client";
import {Calendar, Description} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
return (
{(day) => {day} }
{(date) => }
日历已禁用
);
}
```
### 只读
```tsx
"use client";
import {Calendar, Description} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function ReadOnly() {
return (
{(day) => {day} }
{(date) => }
日历为只读
);
}
```
### 默认值
```tsx
"use client";
import {Calendar} from "@heroui/react";
import {parseDate} from "@internationalized/date";
export function DefaultValue() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### 年份选择
`Calendar.YearPickerTrigger`、`Calendar.YearPickerGrid` 及其 body/cell 子组件提供集成的年份导航模式。
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function YearPicker() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 受控组件
使用受控的 `value` 与 `focusedValue` 进行外部状态协调与自定义快捷操作。
```tsx
"use client";
import type {CalendarDate} from "@internationalized/date";
import {Button, ButtonGroup, Calendar, Description} from "@heroui/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);
}}
>
今天
{
const nextWeekStart = startOfWeek(today(getLocalTimeZone()), locale);
setValue(nextWeekStart);
setFocusedDate(nextWeekStart);
}}
>
本周
{
const nextMonthStart = startOfMonth(today(getLocalTimeZone()));
setValue(nextMonthStart);
setFocusedDate(nextMonthStart);
}}
>
本月
{(day) => {day} }
{(date) => }
已选日期:{value ? value.toString() : "(未选)"}
{
const todayDate = today(getLocalTimeZone());
setValue(todayDate);
setFocusedDate(todayDate);
}}
>
设为今天
{
const christmasDate = parseDate("2025-12-25");
setValue(christmasDate);
setFocusedDate(christmasDate);
}}
>
设为圣诞节
setValue(null)}>
清空
);
}
```
### 日期范围限制
```tsx
"use client";
import {Calendar, Description} from "@heroui/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) => }
请在今天与 {maxDate.toString()} 之间选择日期。
);
}
```
### 不可用日期
使用 `isDateUnavailable` 阻止周末、节假日或已预订时段等日期。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar, Description} from "@heroui/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) => }
周末不可选
);
}
```
### 月份周数
将 `weeksInMonth` 设为固定值(如 `6`)可在月份切换时保持网格高度稳定。在非公历 locale 中请谨慎使用,类似 `firstDayOfWeek`。
```tsx
"use client";
import {Calendar, Description} from "@heroui/react";
export function WeeksInMonth() {
return (
{(day) => {day} }
{(date) => }
每月固定显示 6 周,切换月份时避免布局跳动
);
}
```
### 周视图
设置 `visibleDuration={{ weeks: n }}` 可一次显示一周或多周。导航按可见周范围前进。显示多周时使用 `pageBehavior="single"` 可每次移动一周。
```tsx
"use client";
import {Calendar, Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
const weekOptions = [
{id: "1", name: "1 周"},
{id: "2", name: "2 周"},
{id: "3", name: "3 周"},
{id: "4", name: "4 周"},
{id: "5", name: "5 周"},
{id: "6", name: "6 周"},
{id: "8", name: "8 周"},
] as const;
export function WeekView() {
const [weeks, setWeeks] = useState(1);
return (
value && setWeeks(Number(value))}
>
可见周数
{weekOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### 日视图
设置 `visibleDuration={{ days: n }}` 可显示连续日期的滚动窗口。导航按可见日范围前进。显示多天时使用 `pageBehavior="single"` 可每次移动一天。
```tsx
"use client";
import {Calendar, Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
const dayOptions = [
{id: "1", name: "1 天"},
{id: "5", name: "5 天"},
{id: "7", name: "7 天"},
{id: "8", name: "8 天"},
{id: "10", name: "10 天"},
{id: "14", name: "14 天"},
{id: "21", name: "21 天"},
] as const;
export function DayView() {
const [days, setDays] = useState(5);
return (
value && setDays(Number(value))}
>
可见天数
{dayOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### 多选
设置 `selectionMode="multiple"` 允许选择多个日期。`value`、`defaultValue` 与 `onChange` 使用日期数组。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar, Description} from "@heroui/react";
import {useState} from "react";
export function MultipleSelection() {
const [value, setValue] = useState([]);
return (
{(day) => {day} }
{(date) => }
{value?.length ? `已选择 ${value.length} 个日期` : "可选择多个日期"}
);
}
```
### 聚焦值
使用 `focusedValue` 与 `onFocusChange` 以编程方式控制聚焦日期。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Calendar, Description} from "@heroui/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) => }
聚焦:{focusedDate.toString()}
setFocusedDate(parseDate("2025-01-01"))}
>
跳转到一月
setFocusedDate(parseDate("2025-06-15"))}
>
跳转到六月
setFocusedDate(parseDate("2025-12-25"))}
>
跳转到圣诞节
);
}
```
### 单元格标记
可自定义 `Calendar.Cell` 子节点,并使用 `Calendar.CellIndicator` 显示事件等元数据。
```tsx
"use client";
import {Calendar} from "@heroui/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)) && (
)}
>
)}
)}
);
}
```
### 自定义导航图标
向 `Calendar.NavButton` 传入子节点以替换默认 chevron 图标。
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function CustomIcons() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### 多月份展示
使用 `visibleDuration` 与 `offset` 渲染多个月份网格,适用于预订与规划场景。
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function MultipleMonths() {
return (
{(day) => {day} }
{(date) => }
{(day) => {day} }
{(date) => }
);
}
```
### 典型场景
```tsx
"use client";
import type {CalendarDate, DateValue} from "@internationalized/date";
import {Button, Calendar} from "@heroui/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) && }
>
)}
)}
已有预订
周末/不可用
{selectedDate ? (
预订 {selectedDate.toString()}
) : null}
);
}
```
### 国际化日历
默认情况下,Calendar 使用用户 locale 的历法系统显示日期。可用 `I18nProvider` 包裹 Calendar 并设置 [Unicode 历法 locale 扩展](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string) 来覆盖。
以下示例展示印度历法系统:
```tsx
"use client";
import {Calendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**Note:** `onChange` 事件始终返回与 `value` 或 `defaultValue` 相同历法系统的日期(未提供 value 时为公历),无论显示 locale 如何。这确保应用逻辑在单一历法系统下一致运行,同时仍以用户偏好的格式显示日期。
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Calendar} from "@heroui/react";
export function CustomStyles() {
return (
{(day) => (
{day}
)}
{(date) => (
)}
);
}
```
### 全局 CSS
```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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Calendar 在 `packages/styles/components/calendar.css` 与 `packages/styles/components/calendar-year-picker.css` 中使用以下类:
* `.calendar` - 根容器
* `.calendar__header` - 包含导航按钮与标题的头部行
* `.calendar__heading` - 当前月份标签
* `.calendar__nav-button` - 上/下月导航控件
* `.calendar__grid` - 主日期网格
* `.calendar__grid-header` - 星期标题行包装器
* `.calendar__grid-body` - 日期行包装器
* `.calendar__header-cell` - 星期标题单元格
* `.calendar__cell` - 可交互的日期单元格
* `.calendar__cell-indicator` - 日期单元格内的点指示器
* `.calendar-year-picker__trigger` - 年份选择器切换按钮
* `.calendar-year-picker__trigger-heading` - 年份选择器触发器内的标题文本
* `.calendar-year-picker__trigger-indicator` - 年份选择器触发器内的指示图标
* `.calendar-year-picker__year-grid` - 可选年份的覆盖网格
* `.calendar-year-picker__year-cell` - 单个年份选项
### 交互状态
Calendar 同时支持伪类与 React Aria data 属性:
* **Selected**:`[data-selected="true"]`
* **Today**:`[data-today="true"]`
* **Unavailable**:`[data-unavailable="true"]`
* **Outside month**:`[data-outside-month="true"]`
* **Hovered**:`:hover` 或 `[data-hovered="true"]`
* **Pressed**:`:active` 或 `[data-pressed="true"]`
* **Focus visible**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:`:disabled` 或 `[data-disabled="true"]`
## API 参考
### Calendar
Calendar 继承 React Aria [Calendar](https://react-spectrum.adobe.com/react-aria/Calendar.html) 的所有属性。
| Prop | 类型 | 默认值 | 描述 |
| ------------------------ | --------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------ |
| `selectionMode` | `'single' \| 'multiple'` | `'single'` | 是否可选择单个或多个日期 |
| `value` | `DateValue \| null` 或 `DateValue[] \| null` | - | 受控选中日期。`selectionMode` 为 `multiple` 时使用数组 |
| `defaultValue` | `DateValue \| null` 或 `DateValue[] \| null` | - | 初始选中日期(非受控) |
| `onChange` | `(value: DateValue \| null)` 或 `(value: DateValue[] \| null) => void` | - | 选择变化时调用 |
| `focusedValue` | `DateValue` | - | 受控聚焦日期 |
| `onFocusChange` | `(value: DateValue) => void` | - | 焦点移至其他日期时调用 |
| `minValue` | `DateValue` | 历法感知的 `1900-01-01` | 最早可选日期 |
| `maxValue` | `DateValue` | 历法感知的 `2099-12-31` | 最晚可选日期 |
| `weeksInMonth` | `number` | - | 月份中的周数,覆盖 locale 默认值 |
| `isDateUnavailable` | `(date: DateValue) => boolean` | - | 标记日期为不可用 |
| `firstDayOfWeek` | `'sun' \| 'mon' \| 'tue' \| 'wed' \| 'thu' \| 'fri' \| 'sat'` | - | 覆盖 locale 默认的一周起始日 |
| `pageBehavior` | `'visible' \| 'single'` | `'visible'` | 翻页按可见时长还是单个单位前进 |
| `selectionAlignment` | `'start' \| 'center' \| 'end'` | `'center'` | 初始渲染时可见范围与选择的对齐方式 |
| `isDisabled` | `boolean` | `false` | 禁用交互与选择 |
| `isReadOnly` | `boolean` | `false` | 内容可读但不可更改选择 |
| `isInvalid` | `boolean` | `false` | 标记日历为无效以显示验证 UI |
| `visibleDuration` | `{months?: number; weeks?: number; days?: number}` | `{months: 1}` | 可见时间范围。月视图用 `{ months: n }`,周视图用 `{ weeks: n }`,日视图用 `{ days: n }` |
| `defaultYearPickerOpen` | `boolean` | `false` | 内部年份选择器初始打开状态 |
| `isYearPickerOpen` | `boolean` | - | 受控年份选择器打开状态 |
| `onYearPickerOpenChange` | `(isOpen: boolean) => void` | - | 年份选择器打开状态变化时调用 |
### Composition Parts
| Component | Description |
| ------------------------------------- | ----------------------------------------------- |
| `Calendar.Header` | 导航与标题的头部容器 |
| `Calendar.Heading` | 可见范围的格式化标题。支持 `offset`(多月布局)与 `format`(月/年/日选项) |
| `Calendar.NavButton` | 上/下月导航控件(`slot="previous"` 或 `slot="next"`) |
| `Calendar.Grid` | 单个月份的日期网格(多月布局支持 `offset`) |
| `Calendar.GridHeader` | 星期标题容器 |
| `Calendar.GridBody` | 日期单元格 body 容器 |
| `Calendar.HeaderCell` | 星期标签单元格 |
| `Calendar.Cell` | 单个日期单元格 |
| `Calendar.CellIndicator` | 自定义元数据的可选指示元素 |
| `Calendar.YearPickerTrigger` | 切换年份选择器模式的触发器 |
| `Calendar.YearPickerTriggerHeading` | 年份选择器触发器内的本地化标题内容 |
| `Calendar.YearPickerTriggerIndicator` | 年份选择器触发器内的切换图标 |
| `Calendar.YearPickerGrid` | 覆盖式年份选择网格容器 |
| `Calendar.YearPickerGridBody` | 年份网格单元格的 body 渲染器 |
| `Calendar.YearPickerCell` | 单个年份选项单元格 |
### Year Picker Parts
年份选择器子组件继承 React Aria [`useCalendarHeading`](https://react-aria.adobe.com/useCalendar#usecalendarheading) 与 [`useCalendarYearPicker`](https://react-aria.adobe.com/useCalendar#usecalendaryearpicker) 的格式化属性。
| Component | Prop | 类型 | 默认值 | 描述 |
| ----------------------------------- | -------------- | ---------------------- | ------------------- | ------------------------------------------------- |
| `Calendar.YearPickerTriggerHeading` | `format` | `DateFormatterOptions` | - | 自定义月/年标签(如 `{month: 'short'}`) |
| `Calendar.YearPickerTriggerHeading` | `offset` | `{months?: number}` | - | 相对聚焦日期偏移标题(多月布局) |
| `Calendar.YearPickerGrid` | `format` | `DateFormatterOptions` | `{year: 'numeric'}` | 自定义年份单元格标签(纪元、历法等) |
| `Calendar.YearPickerGrid` | `visibleYears` | `number` | min–max 跨度或 `20` | 滑动窗口中显示的年份数。同时设置 `minValue` 与 `maxValue` 时默认为完整范围 |
### Calendar.Cell Render
`Calendar.Cell` 子节点为函数时,可使用 React Aria render props:
| Prop | 类型 | 描述 |
| ---------------- | --------- | ----------- |
| `formattedDate` | `string` | 单元格的本地化日期标签 |
| `isSelected` | `boolean` | 日期是否选中 |
| `isUnavailable` | `boolean` | 日期是否不可用 |
| `isDisabled` | `boolean` | 单元格是否禁用 |
| `isOutsideMonth` | `boolean` | 日期是否属于相邻月份 |
有关支持的历法系统及其标识符的完整列表,请参阅:
* [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/) — 所有日期组件使用的日期类型(`CalendarDate`、`CalendarDateTime`、`ZonedDateTime`)与工具
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — 覆盖子树的 locale
* [`useLocale`](https://react-aria.adobe.com/useLocale) — 读取当前 locale 与布局方向
## 相关组件
## 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
# DateField 日期字段
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/date-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(date-and-time)/date-field.mdx
> 基于 React Aria DateField 的日期输入字段,包含标签、说明与校验
## 用法
```tsx
import { DateField } from '@heroui/react';
```
```tsx
"use client";
import {DateField, Label} from "@heroui/react";
export function Basic() {
return (
日期
{(segment) => }
);
}
```
## 组件结构
```tsx
import {DateField, Label, Description, FieldError} from '@heroui/react';
export default () => (
{(segment) => }
)
```
> **DateField** 将标签、日期输入、说明与错误信息组合为单个无障碍组件。
## 示例
### 带图标
通过前缀或后缀图标增强日期输入。
```tsx
"use client";
import {Calendar} from "@gravity-ui/icons";
import {DateField, Label} from "@heroui/react";
export function WithPrefixIcon() {
return (
日期
{(segment) => }
);
}
```
```tsx
"use client";
import {Calendar} from "@gravity-ui/icons";
import {DateField, Label} from "@heroui/react";
export function WithSuffixIcon() {
return (
日期
{(segment) => }
);
}
```
```tsx
"use client";
import {Calendar, ChevronDown} from "@gravity-ui/icons";
import {DateField, Description, Label} from "@heroui/react";
export function WithPrefixAndSuffix() {
return (
日期
{(segment) => }
输入日期
);
}
```
### 变体
DateField.Group 组件支持两种视觉变体:
* **`primary`**(默认)- 标准样式带阴影,适用于大多数场景
* **`secondary`** - 低强调变体无阴影,适用于 Surface 组件内
```tsx
"use client";
import {DateField, Label} from "@heroui/react";
export function Variants() {
return (
主要变体
{(segment) => }
次要变体
{(segment) => }
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请在 DateField.Group 上使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
"use client";
import {Calendar} from "@gravity-ui/icons";
import {DateField, Description, Label, Surface} from "@heroui/react";
export function OnSurface() {
return (
日期
{(segment) => }
输入日期
预约日期
{(segment) => }
输入预约日期
);
}
```
### 带描述
```tsx
"use client";
import {DateField, Description, Label} from "@heroui/react";
export function WithDescription() {
return (
出生日期
{(segment) => }
输入出生日期
预约日期
{(segment) => }
输入预约日期
);
}
```
### 必填字段
```tsx
"use client";
import {DateField, Description, Label} from "@heroui/react";
export function Required() {
return (
日期
{(segment) => }
开始日期
{(segment) => }
必填项
);
}
```
### 禁用
```tsx
"use client";
import {DateField, Description, Label} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
return (
日期
{(segment) => }
该日期字段已禁用
日期
{(segment) => }
该日期字段已禁用
);
}
```
### 宽度充满
```tsx
"use client";
import {Calendar, ChevronDown} from "@gravity-ui/icons";
import {DateField, Label} from "@heroui/react";
export function FullWidth() {
return (
日期
{(segment) => }
日期
{(segment) => }
);
}
```
### 表单校验
配合 `FieldError` 使用 `isInvalid` 展示校验消息。
```tsx
"use client";
import {DateField, FieldError, Label} from "@heroui/react";
export function Invalid() {
return (
日期
{(segment) => }
请输入有效日期
日期
{(segment) => }
日期须为将来
);
}
```
### 时间粒度
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {CircleQuestion} from "@gravity-ui/icons";
import {DateField, Label, ListBox, Select, Tooltip} from "@heroui/react";
import {parseDate, parseZonedDateTime} from "@internationalized/date";
import {useState} from "react";
export function Granularity() {
const granularityOptions = [
{id: "day", label: "日"},
{id: "hour", label: "时"},
{id: "minute", label: "分"},
{id: "second", label: "秒"},
] 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 (
预约日期
{(segment) => }
粒度
决定日期选择器显示的最小单位。默认情况下,日期为「日」,时间为「分」。
setGranularity(value as typeof granularity)}
>
{granularityOptions.map((option) => (
{option.label}
))}
);
}
```
### 受控组件
控制 value 以与其他组件或状态管理同步。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, DateField, Description, Label} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(null);
return (
日期
{(segment) => }
当前值:{value ? value.toString() : "(空)"}
setValue(today(getLocalTimeZone()))}>
设为今天
setValue(null)}>
清空
);
}
```
### 表单示例
包含校验与提交的完整表单示例。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar} from "@gravity-ui/icons";
import {Button, DateField, Description, FieldError, Form, Label} from "@heroui/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 todayDate = today(getLocalTimeZone());
const isInvalid = value !== null && value.compare(todayDate) < 0;
const handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("已提交日期:", {date: value});
setValue(null);
setIsSubmitting(false);
}, 1500);
};
return (
预约日期
{(segment) => }
{isInvalid ? (
日期须为今天或将来
) : (
输入日期 from today onwards
)}
{isSubmitting ? "提交中…" : "Submit"}
);
}
```
### 带校验
DateField 支持 `minValue`、`maxValue` 与自定义校验逻辑。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {DateField, Description, FieldError, Label} from "@heroui/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 (
日期
{(segment) => }
{isInvalid ? (
日期须为今天或将来
) : (
输入日期 from today onwards
)}
);
}
```
### 渲染函数
```tsx
"use client";
import {DateField, Label} from "@heroui/react";
export function RenderFunction() {
return (
}
>
}>日期
}>
}>
{(segment) => }
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {DateField, Label} from "@heroui/react";
export function CustomStyles() {
return (
截止日期
{(segment) => }
);
}
```
### 全局 CSS
DateField 默认样式较轻量。覆盖 `.date-field` 类可自定义容器样式。
```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;
}
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
* `.date-field` – 轻量样式的根容器(`flex flex-col gap-1`)
> **Note:** 子组件([Label](/docs/react/components/label)、[Description](/docs/react/components/description)、[FieldError](/docs/react/components/field-error))拥有各自的 CSS 类与样式。自定义方式请参阅对应文档。DateField.Group 样式见下文 API 参考。
### 交互状态
DateField 会根据状态自动设置以下 data 属性:
* **Invalid**:`[data-invalid="true"]` 或 `[aria-invalid="true"]` - 无效时自动隐藏 description slot
* **Required**:`[data-required="true"]` - 当 `isRequired` 为 true 时应用
* **Disabled**:`[data-disabled="true"]` - 当 `isDisabled` 为 true 时应用
* **Focus Within**:`[data-focus-within="true"]` - 任一子输入聚焦时应用
## API 参考
### DateField
DateField 继承 React Aria [DateField](https://react-aria.adobe.com/DateField.md) 的全部 props。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------ | ------- | ---------------------------------- |
| `children` | `React.ReactNode \| (values: DateFieldRenderProps) => React.ReactNode` | - | 子组件(Label、DateField.Group 等)或渲染函数。 |
| `className` | `string \| (values: DateFieldRenderProps) => string` | - | 用于样式的 CSS 类,支持渲染 prop。 |
| `style` | `React.CSSProperties \| (values: DateFieldRenderProps) => React.CSSProperties` | - | 内联样式,支持渲染 prop。 |
| `fullWidth` | `boolean` | `false` | 日期字段是否占满容器宽度。 |
| `id` | `string` | - | 元素的唯一 id。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
#### Value Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | ------------------------------------ | --- | ----------------------------------------------------------------------------------------------- |
| `value` | `DateValue \| null` | - | 当前值(受控)。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `defaultValue` | `DateValue \| null` | - | 默认值(非受控)。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `onChange` | `(value: DateValue \| null) => void` | - | 值变化时触发的事件处理函数。 |
| `placeholderValue` | `DateValue \| null` | - | 影响占位符格式的占位日期。 |
#### Validation Props
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | -------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------ |
| `isRequired` | `boolean` | `false` | 是否在提交表单前要求用户输入。 |
| `isInvalid` | `boolean` | - | 值是否无效。 |
| `minValue` | `DateValue \| null` | - | 用户可选择最早日期。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `maxValue` | `DateValue \| null` | - | 用户可选择最晚日期。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `isDateUnavailable` | `(date: DateValue) => boolean` | - | 针对每个日期调用;返回 true 表示该日期不可用。 |
| `validate` | `(value: DateValue) => ValidationError \| true \| null \| undefined` | - | 自定义校验函数。 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 表单校验还是 ARIA 属性。 |
#### Format Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------------- | ------------- | ------- | --------------------------------------- |
| `granularity` | `Granularity` | - | 显示的最小单位。日期默认为 `"day"`,时间默认为 `"minute"`。 |
| `hourCycle` | `12 \| 24` | - | 以 12 或 24 小时制显示时间;默认由语言环境决定。 |
| `hideTimeZone` | `boolean` | `false` | 是否隐藏时区缩写。 |
| `shouldForceLeadingZeros` | `boolean` | - | 是否始终为月、日、小时等显示前导零。 |
#### State Props
| Prop | 类型 | 默认值 | 描述 |
| ------------ | --------- | --- | ----------- |
| `isDisabled` | `boolean` | - | 是否禁用输入。 |
| `isReadOnly` | `boolean` | - | 是否可选中但不可修改。 |
#### Form Props
| Prop | 类型 | 默认值 | 描述 |
| -------------- | --------- | --- | ----------------------------------------- |
| `name` | `string` | - | 输入元素的 name,用于 HTML 表单提交;以 ISO 8601 字符串提交。 |
| `autoFocus` | `boolean` | - | 是否在渲染后自动聚焦该元素。 |
| `autoComplete` | `string` | - | 输入应提供的自动完成类型。 |
#### Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------- | --- | ------------- |
| `aria-label` | `string` | - | 无可见标签时的无障碍标签。 |
| `aria-labelledby` | `string` | - | 标注该字段的元素 id。 |
| `aria-describedby` | `string` | - | 描述该字段的元素 id。 |
| `aria-details` | `string` | - | 包含额外详情的元素 id。 |
### Composition Components
DateField 与以下独立组件配合使用,请分别导入并直接使用:
* **Label** – 来自 `@heroui/react` 的字段标签
* **DateField.Group** – 日期输入分组(详见下文)
* **DateField.Input** – 来自 `@heroui/react` 的分段位编辑输入
* **DateField.InputContainer** – 可横向滚动的容器,用于组合多个输入(例如开始/结束范围)
* **DateField.Segment** – 单个日期段位(年、月、日等)
* **DateField.Prefix** / **DateField.Suffix** – 输入组的前缀与后缀插槽
* **Description** – 来自 `@heroui/react` 的辅助说明
* **FieldError** – 来自 `@heroui/react` 的校验错误信息
这些组件各自有独立的 props API。在 DateField 中直接组合使用:
```tsx
import {parseDate} from '@internationalized/date';
import {DateField, Label, Description, FieldError} from '@heroui/react';
Appointment Date
{(segment) => }
Select a date from today onwards.
Please select a valid date.
```
### DateValue Types
DateField 使用 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) 中的类型:
* `CalendarDate` – 不含时间与时区的日期
* `CalendarDateTime` – 含时间、不含时区
* `ZonedDateTime` – 含时间与时区
* `Time` – 仅时间
示例:
```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
{/* ... */}
```
> **说明:** DateField 依赖 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) 进行解析、运算与类型定义。更多类型与函数见 [Internationalized Date 文档](https://react-aria.adobe.com/internationalized/date/)。
### Render Props
对 `className`、`style` 或 `children` 使用渲染 prop 时,可使用以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | ------------- |
| `isDisabled` | `boolean` | 字段是否禁用。 |
| `isInvalid` | `boolean` | 字段当前是否无效。 |
| `isReadOnly` | `boolean` | 字段是否只读。 |
| `isRequired` | `boolean` | 字段是否必填。 |
| `isFocused` | `boolean` | 字段是否聚焦。 |
| `isFocusWithin` | `boolean` | 是否有子元素聚焦。 |
| `isFocusVisible` | `boolean` | 焦点是否可见(键盘导航)。 |
### DateField.Group
DateField.Group 继承 React Aria `Group` 的全部 props,并额外支持:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------- | ----------- | ---------------------------------------------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `fullWidth` | `boolean` | `false` | 日期输入组是否占满容器宽度。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调、无阴影,适合用于 Surface。 |
### DateField.Input
DateField.Input 继承 React Aria `DateInput` 的全部 props,并额外支持:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------- | ----------- | ------------------------------------------------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 输入的视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调、无阴影,适合用于 Surface。 |
`DateField.Input` 接受渲染函数作为子节点,函数参数为日期段位;每个段位对应日期的一部分(年、月、日等)。
### DateField.Segment
DateField.Segment 继承 React Aria `DateSegment` 的全部 props:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------- | --- | ------------------------------------------ |
| `segment` | `DateSegment` | - | 来自 DateField.Input 渲染函数的 `DateSegment` 对象。 |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
### DateField.InputContainer
DateField.InputContainer 接受标准 HTML `div` 属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ---------------------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 滚动容器中的内容(通常为多个 `DateField.Input`)。 |
### DateField.Prefix
DateField.Prefix 接受标准 HTML `div` 属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------------ |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 前缀插槽中要显示的内容。 |
### DateField.Suffix
DateField.Suffix 接受标准 HTML `div` 属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------------ |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 后缀插槽中要显示的内容。 |
## DateField.Group Styling
### Customizing the component classes
基础类作用于所有实例,可通过 `@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` – 根容器样式
* `.date-input-group__input` – 输入包裹层样式
* `.date-input-group__input-container` – 用于组合多个输入的滚动容器
* `.date-input-group__segment` – 单个日期段位样式
* `.date-input-group__prefix` – 前缀元素样式
* `.date-input-group__suffix` – 后缀元素样式
### DateField.Group Interactive States
* **Hover**:`:hover` 或 `[data-hovered="true"]`
* **Focus Within**:`[data-focus-within="true"]` 或 `:focus-within`
* **Invalid**:`[data-invalid="true"]`(与 `aria-invalid` 同步)
* **Disabled**:`[data-disabled="true"]` 或 `[aria-disabled="true"]`
* **Segment Focus**:段位上 `:focus` 或 `[data-focused="true"]`
* **Segment Placeholder**:段位上 `[data-placeholder="true"]`
## 相关组件
## 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
# DatePicker 日期选择器
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/date-picker
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(date-and-time)/date-picker.mdx
> 基于 React Aria DatePicker,通过 DateField 与 Calendar 组合的可组合日期选择器
## 用法
```tsx
import { DatePicker, DateField, Calendar, Label } from '@heroui/react';
```
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@heroui/react";
export function Basic() {
return (
日期
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
## 组件结构
`DatePicker` 采用组合优先 API。显式组合 `DateField` 与 `Calendar` 以控制结构与样式。
```tsx
import {Calendar, DateField, DatePicker, Label} from '@heroui/react';
export default () => (
{(segment) => }
{(day) => {day} }
{(date) => }
)
```
## 示例
### 禁用
```tsx
"use client";
import {Calendar, DateField, DatePicker, Description, Label} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
return (
日期
{(segment) => }
该日期选择器已禁用。
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 受控组件
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Calendar, DateField, DatePicker, Description, Label} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(today(getLocalTimeZone()));
return (
日期
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
当前值:{value ? value.toString() : "(空)"}
setValue(today(getLocalTimeZone()))}>
设为今天
setValue(null)}>
清空
);
}
```
### 表单校验
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Calendar, DateField, DatePicker, FieldError, Label} from "@heroui/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 (
预约日期
{(segment) => }
日期须为今天或将来。
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 格式选项
使用 `granularity`、`hourCycle`、`hideTimeZone`、`shouldForceLeadingZeros` 等 props 控制 DatePicker 值的显示方式。
```tsx
"use client";
import type {TimeValue} from "@heroui/react";
import type {DateValue} from "@internationalized/date";
import {
Calendar,
DateField,
DatePicker,
Label,
ListBox,
Select,
Switch,
TimeField,
} from "@heroui/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: "日", value: "day"},
{label: "时", value: "hour"},
{label: "分", value: "minute"},
{label: "秒", value: "second"},
];
const hourCycleOptions: {label: string; value: HourCycle}[] = [
{label: "12 小时制", value: 12},
{label: "24 小时制", 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}) => (
<>
日期和时间
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
{!!showTimeField && (
时间
state.setTimeValue(v as TimeValue)}
>
{(segment) => }
)}
>
)}
setGranularity(value as Granularity)}
>
粒度
{granularityOptions.map((option) => (
{option.label}
))}
setHourCycle(Number(value) as HourCycle)}
>
小时制
{hourCycleOptions.map((option) => (
{option.label}
))}
隐藏时区
强制前导零
);
}
```
### 表单示例
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {
Button,
Calendar,
DateField,
DatePicker,
Description,
FieldError,
Form,
Label,
} from "@heroui/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.SyntheticEvent) => {
event.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
setTimeout(() => {
setValue(null);
setIsSubmitting(false);
}, 1200);
};
return (
预约日期
{(segment) => }
{isInvalid ? (
日期须为今天或将来。
) : (
请选择有效的预约日期。
)}
{(day) => {day} }
{(date) => }
{({year}) => }
{isSubmitting ? "提交中…" : "提交"}
);
}
```
### 自定义指示器
未提供 children 时,`DatePicker.TriggerIndicator` 渲染默认 `IconCalendar`。传入 children 可替换。
```tsx
"use client";
import {Calendar, DateField, DatePicker, Description, Label} from "@heroui/react";
import {Icon} from "@iconify/react";
export function WithCustomIndicator() {
return (
日期
{(segment) => }
通过传入自定义子元素替换默认日历图标。
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 渲染函数
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@heroui/react";
export function RenderFunction() {
return (
}
>
}>日期
}
>
}>
{(segment) => (
}
segment={segment}
/>
)}
}
>
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 国际化日历
默认情况下,DatePicker 使用用户 locale 的日历系统显示日期。可用 `I18nProvider` 包裹并设置 [Unicode 日历 locale 扩展](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string) 覆盖。
以下示例展示印度日历系统:
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
return (
活动日期
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**Note:** 无论显示的 locale 如何,`onChange` 事件始终返回与 `value` 或 `defaultValue` 相同日历系统的日期(未提供 value 时为 Gregorian)。这确保应用逻辑在单一日历系统下一致运行,同时仍可按用户偏好格式显示日期。
完整支持的日历系统及其标识符列表请参阅:
* [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)
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Calendar, DateField, DatePicker, Label} from "@heroui/react";
export function CustomStyles() {
return (
活动日期
{(segment) => }
{(day) => (
{day}
)}
{(date) => (
)}
);
}
```
### 全局 CSS
使用 `@layer components` 自定义 DatePicker 基础类。
```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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 命名以便复用自定义。
### CSS 类
DatePicker 在 `packages/styles/components/date-picker.css` 中使用以下类:
* `.date-picker` - 根包裹层
* `.date-picker__trigger` - 打开 popover 的触发器部分
* `.date-picker__trigger-indicator` - 默认/自定义指示器 slot
* `.date-picker__popover` - Popover 内容包裹层
### 交互状态
DatePicker 支持 React Aria data 属性与伪状态:
* **Open**:触发器上 `[data-open="true"]`
* **Disabled**:触发器上 `[data-disabled="true"]` 或 `[aria-disabled="true"]`
* **Focus visible**:触发器上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **Hover**:触发器上 `:hover` 或 `[data-hovered="true"]`
## API 参考
### DatePicker
DatePicker 继承 React Aria [DatePicker](https://react-aria.adobe.com/DatePicker.md) 的所有 props。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ----------------------------------------------------------------------------- | ------- | -------------------------- |
| `value` | `DateValue \| null` | - | 受控选中日期值 |
| `defaultValue` | `DateValue \| null` | - | 非受控模式下的默认选中值 |
| `onChange` | `(value: DateValue \| null) => void` | - | 选中日期变化时调用 |
| `isOpen` | `boolean` | - | 受控 popover 打开状态 |
| `defaultOpen` | `boolean` | `false` | 初始 popover 打开状态 |
| `onOpenChange` | `(isOpen: boolean) => void` | - | popover 打开状态变化时调用 |
| `isDisabled` | `boolean` | `false` | 禁用日期选择与触发器交互 |
| `isInvalid` | `boolean` | - | 标记字段为无效以显示校验状态 |
| `minValue` | `DateValue` | - | 最小可选日期 |
| `maxValue` | `DateValue` | - | 最大可选日期 |
| `name` | `string` | - | HTML 表单提交时使用的 name |
| `children` | `ReactNode \| (values: DatePickerRenderProps) => ReactNode` | - | 组合内容或 render 函数 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Composition Parts
| Component | Description |
| ----------------------------- | ------------------------------- |
| `DatePicker.Root` | 根 date picker 容器与状态所有者 |
| `DatePicker.Trigger` | 触发按钮,通常渲染在 `DateField.Suffix` 内 |
| `DatePicker.TriggerIndicator` | 带默认日历图标的指示器 slot |
| `DatePicker.Popover` | `Calendar` 内容的 Popover 包裹层 |
### Related packages
* [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — 所有日期组件使用的日期类型(`CalendarDate`、`CalendarDateTime`、`ZonedDateTime`)与工具
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — 为子树覆盖 locale
* [`useLocale`](https://react-aria.adobe.com/useLocale) — 读取当前 locale 与布局方向
## 相关组件
## 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
# DateRangePicker 日期范围选择器
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/date-range-picker
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(date-and-time)/date-range-picker.mdx
> 基于 React Aria DateRangePicker,通过 DateField 与 RangeCalendar 组合的可组合日期范围选择器
## 用法
```tsx
import { DateField, DateRangePicker, Label, RangeCalendar } from '@heroui/react';
```
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@heroui/react";
export function Basic() {
return (
出行日期
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
## 组件结构
`DateRangePicker` 采用组合优先 API。显式组合 `DateField` 与 `RangeCalendar` 以控制结构与样式。
```tsx
import {DateField, DateRangePicker, Label, RangeCalendar} from '@heroui/react';
export default () => (
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
)
```
## 示例
### 禁用
```tsx
"use client";
import {DateField, DateRangePicker, Description, Label, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function Disabled() {
const start = today(getLocalTimeZone());
return (
出行日期
{(segment) => }
{(segment) => }
该日期范围选择器已禁用。
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 受控组件
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, DateField, DateRangePicker, Description, Label, RangeCalendar} from "@heroui/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 (
出行日期
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
当前值:{value ? `${value.start.toString()} 至 ${value.end.toString()}` : "(空)"}
{
const nextStart = today(getLocalTimeZone());
setValue({end: nextStart.add({days: 6}), start: nextStart});
}}
>
设为一周
setValue(null)}>
清空
);
}
```
### 表单校验
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {DateField, DateRangePicker, FieldError, Label, RangeCalendar} from "@heroui/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 (
预订时段
{(segment) => }
{(segment) => }
请选择从今天起的有效日期范围。
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 格式选项
使用 `granularity`、`hourCycle`、`hideTimeZone`、`shouldForceLeadingZeros` 等 props 控制 DateRangePicker 值的显示方式。
```tsx
"use client";
import type {TimeValue} from "@heroui/react";
import type {DateValue} from "@internationalized/date";
import {
DateField,
DateRangePicker,
Label,
ListBox,
RangeCalendar,
Select,
Separator,
Switch,
TimeField,
useLocale,
} from "@heroui/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: "日", value: "day"},
{label: "时", value: "hour"},
{label: "分", value: "minute"},
{label: "秒", value: "second"},
];
const hourCycleOptions: {label: string; value: HourCycle}[] = [
{label: "12 小时制", value: 12},
{label: "24 小时制", 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}) => (
<>
日期范围
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
{!!showTimeField && (
开始时间
state.setTimeRange({
end: state.timeRange?.end as TimeValue,
start: v as TimeValue,
})
}
>
{(segment) => }
结束时间
state.setTimeRange({
end: v as TimeValue,
start: state.timeRange?.start as TimeValue,
})
}
>
{(segment) => }
)}
已选:{" "}
{state.value && state.value.start && state.value.end
? formatDate({end: state.value.end, start: state.value.start})
: "未选择日期"}
>
)}
格式选项
setGranularity(value as Granularity)}
>
粒度
{granularityOptions.map((option) => (
{option.label}
))}
setHourCycle(Number(value) as HourCycle)}
>
小时制
{hourCycleOptions.map((option) => (
{option.label}
))}
隐藏时区
强制前导零
);
}
```
### 表单示例
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {
Button,
DateField,
DateRangePicker,
Description,
FieldError,
Form,
Label,
RangeCalendar,
} from "@heroui/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.SyntheticEvent) => {
event.preventDefault();
if (!value || isInvalid) return;
setIsSubmitting(true);
setTimeout(() => {
setValue(null);
setIsSubmitting(false);
}, 1200);
};
return (
出行日期
{(segment) => }
{(segment) => }
{isInvalid ? (
请选择从今天起的有效日期范围。
) : (
选择入住与退房日期。
)}
{(day) => {day} }
{(date) => }
{({year}) => }
{isSubmitting ? "提交中…" : "提交"}
);
}
```
### 自定义指示器
未提供 children 时,`DateRangePicker.TriggerIndicator` 渲染默认 `IconCalendar`。传入 children 可替换。
```tsx
"use client";
import {DateField, DateRangePicker, Description, Label, RangeCalendar} from "@heroui/react";
import {Icon} from "@iconify/react";
export function WithCustomIndicator() {
return (
出行日期
{(segment) => }
{(segment) => }
通过传入自定义子元素替换默认日历图标。
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 渲染函数
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@heroui/react";
export function RenderFunction() {
return (
}
startName="startDate"
>
出行日期
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 国际化日历
默认情况下,DateRangePicker 使用用户 locale 的日历系统显示日期。可用 `I18nProvider` 包裹并设置 [Unicode 日历 locale 扩展](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string) 覆盖。
以下示例展示印度日历系统:
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
const start = today(getLocalTimeZone());
return (
出行日期
{(segment) => }
{(segment) => }
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**Note:** 无论显示的 locale 如何,`onChange` 事件始终返回与 `value` 或 `defaultValue` 相同日历系统的日期(未提供 value 时为 Gregorian)。
完整支持的日历系统及其标识符列表请参阅:
* [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)
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {DateField, DateRangePicker, Label, RangeCalendar} from "@heroui/react";
export function CustomStyles() {
return (
入住日期
{(segment) => }
{(segment) => }
{(day) => (
{day}
)}
{(date) => }
);
}
```
### 全局 CSS
使用 `@layer components` 自定义 DateRangePicker 基础类。
```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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
DateRangePicker 在 `packages/styles/components/date-range-picker.css` 中使用以下类:
* `.date-range-picker` - 根包裹层
* `.date-range-picker__trigger` - 打开 popover 的触发器部分
* `.date-range-picker__trigger-indicator` - 默认/自定义指示器 slot
* `.date-range-picker__range-separator` - 开始与结束日期输入之间的分隔符
* `.date-range-picker__popover` - Popover 内容包裹层
### 交互状态
DateRangePicker 支持 React Aria data 属性与伪状态:
* **Open**:触发器上 `[data-open="true"]`
* **Disabled**:触发器上 `[data-disabled="true"]` 或 `[aria-disabled="true"]`
* **Focus visible**:触发器上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **Hover**:触发器上 `:hover` 或 `[data-hovered="true"]`
## API 参考
### DateRangePicker
DateRangePicker 继承 React Aria [DateRangePicker](https://react-aria.adobe.com/DateRangePicker) 的所有 props。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ---------------------------------------------------------------------------------- | ------- | -------------------------- |
| `value` | `{ start: DateValue; end: DateValue } \| null` | - | 受控选中日期范围值 |
| `defaultValue` | `{ start: DateValue; end: DateValue } \| null` | - | 非受控模式下的默认选中范围 |
| `onChange` | `(value: { start: DateValue; end: DateValue } \| null) => void` | - | 选中范围变化时调用 |
| `isOpen` | `boolean` | - | 受控 popover 打开状态 |
| `defaultOpen` | `boolean` | `false` | 初始 popover 打开状态 |
| `onOpenChange` | `(isOpen: boolean) => void` | - | popover 打开状态变化时调用 |
| `isDisabled` | `boolean` | `false` | 禁用范围选择与触发器交互 |
| `isInvalid` | `boolean` | - | 标记字段为无效以显示校验状态 |
| `minValue` | `DateValue` | - | 最小可选日期 |
| `maxValue` | `DateValue` | - | 最大可选日期 |
| `startName` | `string` | - | HTML 表单提交时开始日期的 name |
| `endName` | `string` | - | HTML 表单提交时结束日期的 name |
| `children` | `ReactNode \| (values: DateRangePickerRenderProps) => ReactNode` | - | 组合内容或 render 函数 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Composition Parts
| Component | Description |
| ---------------------------------- | ------------------------------- |
| `DateRangePicker.Root` | 根 date range picker 容器与状态所有者 |
| `DateRangePicker.Trigger` | 触发按钮,通常渲染在 `DateField.Suffix` 内 |
| `DateRangePicker.TriggerIndicator` | 带默认日历图标的指示器 slot |
| `DateRangePicker.RangeSeparator` | 开始与结束日期输入之间的分隔符部分 |
| `DateRangePicker.Popover` | `RangeCalendar` 内容的 Popover 包裹层 |
### Related packages
* [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — 所有日期组件使用的日期类型(`CalendarDate`、`CalendarDateTime`、`ZonedDateTime`)与工具
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — 为子树覆盖 locale
* [`useLocale`](https://react-aria.adobe.com/useLocale) — 读取当前 locale 与布局方向
## 相关组件
## 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
# RangeCalendar 范围日历
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/range-calendar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(date-and-time)/range-calendar.mdx
> 基于 React Aria RangeCalendar 的可组合日期范围选择器,包含月份网格、导航与年份选择支持。
## 用法
```tsx
import { RangeCalendar } from '@heroui/react';
```
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
export function Basic() {
return (
{(day) => {day} }
{(date) => }
);
}
```
## 组件结构
```tsx
import {RangeCalendar} from '@heroui/react';
export default () => (
{(day) => {day} }
{(date) => }
)
```
## 示例
### 禁用
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
export function Disabled() {
return (
{(day) => {day} }
{(date) => }
区间日历已禁用
);
}
```
### 年份选择
`RangeCalendar.YearPickerTrigger`, `RangeCalendar.YearPickerGrid`, and their body/cell subcomponents provide an integrated year navigation pattern.
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
export function YearPicker() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
### 默认值
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
import {parseDate} from "@internationalized/date";
export function DefaultValue() {
return (
{(day) => {day} }
{(date) => }
);
}
```
### 受控组件
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, ButtonGroup, Description, RangeCalendar} from "@heroui/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);
}}
>
本周
{
const nextWeekStart = startOfWeek(today(getLocalTimeZone()).add({weeks: 1}), locale);
setFocusedDate(nextWeekStart);
}}
>
下周
{
const nextMonthStart = startOfMonth(today(getLocalTimeZone()).add({months: 1}));
setFocusedDate(nextMonthStart);
}}
>
下月
{(day) => {day} }
{(date) => }
已选区间: {value ? `${value.start.toString()} -> ${value.end.toString()}` : "(无)"}
{
const start = today(getLocalTimeZone());
setValue({end: start.add({days: 6}), start});
setFocusedDate(start);
}}
>
设为 1 周
{
const start = parseDate("2025-12-20");
setValue({end: parseDate("2025-12-31"), start});
setFocusedDate(start);
}}
>
设为节假日
setValue(null)}>
清空
);
}
```
### 日期范围限制
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/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) => }
请在今天与 {maxDate.toString()} 之间选择日期。
);
}
```
### 不可用日期
使用 `isDateUnavailable` 屏蔽周末、节假日或已预订等不可用日期。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/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) => }
部分日期不可选
);
}
```
### 基于锚点的不可用日期
选择范围时,`isDateUnavailable` 会收到第二个参数 `anchorDate`(首个选中日期)。可用它限制哪些结束日期有效(例如起始日期后 7 天内)。
```tsx
"use client";
import type {CalendarDate, DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function AnchorUnavailableDates() {
const now = today(getLocalTimeZone());
const isDateUnavailable = (date: DateValue, anchorDate: CalendarDate | null) => {
return anchorDate != null && Math.abs(date.compare(anchorDate)) > 7;
};
return (
{(day) => {day} }
{(date) => }
选择开始日期后,仅前后 7 天内的日期可选
);
}
```
### 月份周数
将 `weeksInMonth` 设为固定值(例如 `6`),在月份切换时保持网格高度稳定。
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
export function WeeksInMonth() {
return (
{(day) => {day} }
{(date) => }
每月固定显示 6 周,切换月份时避免布局跳动
);
}
```
### 周视图
设置 `visibleDuration={{ weeks: n }}` 一次显示一个或多个周。导航按可见周范围前进。显示多周时,使用 `pageBehavior="single"` 每次移动一周。
```tsx
"use client";
import {Label, ListBox, RangeCalendar, Select} from "@heroui/react";
import {useState} from "react";
const weekOptions = [
{id: "1", name: "1 周"},
{id: "2", name: "2 周"},
{id: "3", name: "3 周"},
{id: "4", name: "4 周"},
{id: "5", name: "5 周"},
{id: "6", name: "6 周"},
{id: "8", name: "8 周"},
] as const;
export function WeekView() {
const [weeks, setWeeks] = useState(1);
return (
value && setWeeks(Number(value))}
>
可见周数
{weekOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### 日视图
设置 `visibleDuration={{ days: n }}` 显示连续日期的滚动窗口。导航按可见日范围前进。显示多天时,使用 `pageBehavior="single"` 每次移动一天。
```tsx
"use client";
import {Label, ListBox, RangeCalendar, Select} from "@heroui/react";
import {useState} from "react";
const dayOptions = [
{id: "1", name: "1 天"},
{id: "5", name: "5 天"},
{id: "7", name: "7 天"},
{id: "8", name: "8 天"},
{id: "10", name: "10 天"},
{id: "14", name: "14 天"},
{id: "21", name: "21 天"},
] as const;
export function DayView() {
const [days, setDays] = useState(5);
return (
value && setDays(Number(value))}
>
可见天数
{dayOptions.map((option) => (
{option.name}
))}
{(day) => {day} }
{(date) => }
);
}
```
### 非连续范围
启用 `allowsNonContiguousRanges` 以允许跨越不可用日期进行选择。
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/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) => }
允许跨不可选日期选择非连续区间
);
}
```
### 只读
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function ReadOnly() {
return (
{(day) => {day} }
{(date) => }
区间日历为只读
);
}
```
### 无效状态
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/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 ? (
最长入住时间为 1 周
) : (
请选择最多 7 天的入住区间
)}
);
}
```
### 聚焦值
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Description, RangeCalendar} from "@heroui/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) => }
聚焦: {focusedDate.toString()}
setFocusedDate(parseDate("2025-01-01"))}
>
跳转到一月
setFocusedDate(parseDate("2025-06-15"))}
>
跳转到六月
setFocusedDate(parseDate("2025-12-25"))}
>
跳转到圣诞节
);
}
```
### 单元格标记
可自定义 `RangeCalendar.Cell` 的子内容,并使用 `RangeCalendar.CellIndicator` 展示事件等元数据。
```tsx
"use client";
import {RangeCalendar} from "@heroui/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)) && (
)}
>
)}
)}
);
}
```
### 典型场景
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, RangeCalendar} from "@heroui/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) && }
>
)}
)}
不可订日期
周末/不可用
{selectedRange ? (
预订 {selectedRange.start.toString()} → {selectedRange.end.toString()}
) : null}
);
}
```
### 多月份展示
结合 `visibleDuration` 与 `offset` 渲染多个网格,适用于预订与规划场景。
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
export function MultipleMonths() {
return (
{(day) => {day} }
{(date) => }
{(day) => {day} }
{(date) => }
);
}
```
### 国际化日历
默认情况下,RangeCalendar 使用用户 locale 的历法系统显示日期。可用 `I18nProvider` 包裹组件,并设置 [Unicode 历法 locale 扩展](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string) 覆盖。
以下示例展示印度历法系统:
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
return (
{(day) => {day} }
{(date) => }
{({year}) => }
);
}
```
**注意:** 无论显示 locale 如何,`onChange` 事件始终返回与 `value` 或 `defaultValue` 相同历法系统的日期(未提供 value 时为公历)。
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
import {parseDate} from "@internationalized/date";
const cellClassName = [
"rounded-md",
"**:data-[slot=range-calendar-cell-button]:rounded-md",
"data-[outside-month=true]:text-muted data-[outside-month=true]:opacity-50",
"data-[hovered=true]:not-data-[selected=true]:**:data-[slot=range-calendar-cell-button]:bg-default",
"data-[today=true]:**:data-[slot=range-calendar-cell-button]:bg-success-soft",
"data-[today=true]:**:data-[slot=range-calendar-cell-button]:text-success-soft-foreground",
"data-[today=true]:data-[hovered=true]:not-data-[selected=true]:**:data-[slot=range-calendar-cell-button]:bg-success-soft-hover",
"data-[selected=true]:rounded-none data-[selected=true]:bg-success-soft",
"data-[outside-month=true]:data-[selected=true]:bg-default/20",
"data-[selection-start=true]:rounded-tl-md data-[selection-start=true]:rounded-bl-md",
"data-[selection-end=true]:rounded-tr-md data-[selection-end=true]:rounded-br-md",
"data-[selection-start=true]:**:data-[slot=range-calendar-cell-button]:bg-success",
"data-[selection-start=true]:**:data-[slot=range-calendar-cell-button]:text-success-foreground",
"data-[selection-start=true]:data-[pressed=true]:**:data-[slot=range-calendar-cell-button]:bg-success-hover",
"data-[selection-end=true]:**:data-[slot=range-calendar-cell-button]:bg-success",
"data-[selection-end=true]:**:data-[slot=range-calendar-cell-button]:text-success-foreground",
"data-[selection-end=true]:data-[pressed=true]:**:data-[slot=range-calendar-cell-button]:bg-success-hover",
].join(" ");
export function CustomStyles() {
return (
{(day) => (
{day}
)}
{(date) => }
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
RangeCalendar 在 `packages/styles/components/range-calendar.css` 与 `packages/styles/components/calendar-year-picker.css` 中使用以下类:
* `.range-calendar` - 根容器。
* `.range-calendar__header` - 含导航按钮与标题的头部行。
* `.range-calendar__heading` - 当前月份标签。
* `.range-calendar__nav-button` - 上一月/下一月导航控件。
* `.range-calendar__grid` - 主体日期网格。
* `.range-calendar__grid-header` - 星期标题行外层。
* `.range-calendar__grid-body` - 日期行外层。
* `.range-calendar__header-cell` - 星期标题单元格。
* `.range-calendar__cell` - 可交互日期单元格外层。
* `.range-calendar__cell-button` - 单元格内的可交互日期按钮。
* `.range-calendar__cell-indicator` - 日期单元格内的圆点指示器。
* `.calendar-year-picker__trigger` - 年份选择器切换按钮。
* `.calendar-year-picker__trigger-heading` - 年份选择触发器内的标题文案。
* `.calendar-year-picker__trigger-indicator` - 年份选择触发器内的指示图标。
* `.calendar-year-picker__year-grid` - 可选年份的覆盖网格。
* `.calendar-year-picker__year-cell` - 单个年份选项。
### 交互状态
RangeCalendar 同时支持伪类与 React Aria 的 data 属性:
* **已选中**:`[data-selected="true"]`
* **范围起点**:`[data-selection-start="true"]`
* **范围终点**:`[data-selection-end="true"]`
* **范围内**:`[data-selection-in-range="true"]`
* **今天**:`[data-today="true"]`
* **不可用**:`[data-unavailable="true"]`
* **跨月**:`[data-outside-month="true"]`
* **悬停**:`:hover` 或 `[data-hovered="true"]`
* **按下**:`:active` 或 `[data-pressed="true"]`
* **焦点可见**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **禁用**:`:disabled` 或 `[data-disabled="true"]`
## API 参考
### RangeCalendar
RangeCalendar 继承 React Aria [RangeCalendar](https://react-spectrum.adobe.com/react-aria/RangeCalendar.html) 的全部 props。
| Prop | 类型 | 默认值 | 描述 |
| --------------------------- | ---------------------------------------------------------------- | ------------------ | ---------------------------------------------------------------------- |
| `value` | `RangeValue \| null` | - | 受控的选中范围。 |
| `defaultValue` | `RangeValue \| null` | - | 初始选中范围(非受控)。 |
| `onChange` | `(value: RangeValue) => void` | - | 选中变化时调用。 |
| `focusedValue` | `DateValue` | - | 受控的焦点日期。 |
| `onFocusChange` | `(value: DateValue) => void` | - | 焦点移动到其它日期时调用。 |
| `minValue` | `DateValue` | 历法感知的 `1900-01-01` | 可选的最早日期。 |
| `maxValue` | `DateValue` | 历法感知的 `2099-12-31` | 可选的最晚日期。 |
| `weeksInMonth` | `number` | - | 一个月的周数。该值会覆盖区域设置的默认值。 |
| `isDateUnavailable` | `(date: DateValue, anchorDate: CalendarDate \| null) => boolean` | - | 将日期标记为不可用。`anchorDate` 为当前范围选择中的首个日期。 |
| `firstDayOfWeek` | `'sun' \| 'mon' \| 'tue' \| 'wed' \| 'thu' \| 'fri' \| 'sat'` | - | 覆盖区域设置的一周起始日。 |
| `pageBehavior` | `'visible' \| 'single'` | `'visible'` | 翻页按可见范围或单步前进。 |
| `selectionAlignment` | `'start' \| 'center' \| 'end'` | `'center'` | 初始渲染时按选中项对齐可见范围。 |
| `allowsNonContiguousRanges` | `boolean` | `false` | 允许范围跨越不可用日期。 |
| `isDisabled` | `boolean` | `false` | 禁用交互与选择。 |
| `isReadOnly` | `boolean` | `false` | 内容只读,不可更改选中。 |
| `isInvalid` | `boolean` | `false` | 标记为无效以配合校验样式。 |
| `visibleDuration` | `{months?: number; weeks?: number; days?: number}` | `{months: 1}` | 可见时间范围。使用 `{ months: n }` 为月视图,`{ weeks: n }` 为周视图,`{ days: n }` 为日视图。 |
| `defaultYearPickerOpen` | `boolean` | `false` | 内置年份选择器的初始展开状态。 |
| `isYearPickerOpen` | `boolean` | - | 受控的年份选择器展开状态。 |
| `onYearPickerOpenChange` | `(isOpen: boolean) => void` | - | 年份选择器展开状态变化时调用。 |
### 组合部件
| 组件 | 描述 |
| ------------------------------------------ | --------------------------------------------------- |
| `RangeCalendar.Header` | 导航与标题的头部容器。 |
| `RangeCalendar.Heading` | 可见范围的格式化标题。支持 `offset`(多月份布局)与 `format`(月/年/日格式选项)。 |
| `RangeCalendar.NavButton` | 上一页/下一页导航(`slot="previous"` 或 `slot="next"`)。 |
| `RangeCalendar.Grid` | 单个月的日期网格(多月份布局支持 `offset`)。 |
| `RangeCalendar.GridHeader` | 星期标题容器。 |
| `RangeCalendar.GridBody` | 日期单元格主体容器。 |
| `RangeCalendar.HeaderCell` | 星期标签单元格。 |
| `RangeCalendar.Cell` | 单个日期单元格。 |
| `RangeCalendar.CellIndicator` | 用于自定义元数据的可选指示元素。 |
| `RangeCalendar.YearPickerTrigger` | 切换年份选择模式的触发器。 |
| `RangeCalendar.YearPickerTriggerHeading` | 年份选择触发器内的本地化标题内容。 |
| `RangeCalendar.YearPickerTriggerIndicator` | 年份选择触发器内的切换图标。 |
| `RangeCalendar.YearPickerGrid` | 年份选择覆盖网格容器。 |
| `RangeCalendar.YearPickerGridBody` | 年份网格单元格的 body 渲染器。 |
| `RangeCalendar.YearPickerCell` | 单个年份选项单元格。 |
### 年份选择器子组件
年份选择器子组件继承 React Aria [`useCalendarHeading`](https://react-aria.adobe.com/useCalendar#usecalendarheading) 与 [`useCalendarYearPicker`](https://react-aria.adobe.com/useCalendar#usecalendaryearpicker) 的格式化属性。
| 组件 | 属性 | 类型 | 默认值 | 描述 |
| ---------------------------------------- | -------------- | ---------------------- | ------------------- | ---------------------------------------------------------- |
| `RangeCalendar.YearPickerTriggerHeading` | `format` | `DateFormatterOptions` | - | 自定义月/年标题(如 `{month: 'short'}`)。 |
| `RangeCalendar.YearPickerTriggerHeading` | `offset` | `{months?: number}` | - | 相对聚焦日期偏移标题(多月布局)。 |
| `RangeCalendar.YearPickerGrid` | `format` | `DateFormatterOptions` | `{year: 'numeric'}` | 自定义年份单元格标签(纪元、历法系统等)。 |
| `RangeCalendar.YearPickerGrid` | `visibleYears` | `number` | min–max 跨度或 `20` | 滑动窗口中显示的年份数量。当同时设置 `minValue` 与 `maxValue` 时,默认为二者之间的完整范围。 |
### RangeCalendar.Cell Render
当 `RangeCalendar.Cell` 的 `children` 为函数时,可使用 React Aria 的渲染参数:
| Prop | 类型 | 描述 |
| ------------------ | --------- | ------------ |
| `formattedDate` | `string` | 单元格日期的本地化文案。 |
| `isSelected` | `boolean` | 该日期是否已选中。 |
| `isSelectionStart` | `boolean` | 是否为选中范围的起点。 |
| `isSelectionEnd` | `boolean` | 是否为选中范围的终点。 |
| `isUnavailable` | `boolean` | 该日期是否不可用。 |
| `isDisabled` | `boolean` | 单元格是否禁用。 |
| `isOutsideMonth` | `boolean` | 是否属于相邻月份。 |
支持的历法系统及其标识符完整列表见:
* [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/) — 各日期组件共用的日期类型(`CalendarDate`、`CalendarDateTime`、`ZonedDateTime`)与工具函数
* [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — 为子树覆盖语言环境
* [`useLocale`](https://react-aria.adobe.com/useLocale) — 读取当前语言环境与书写方向
## 相关组件
## 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
# TimeField 时间字段
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/time-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(date-and-time)/time-field.mdx
> 基于 React Aria TimeField 的时间输入字段,包含标签、说明与校验。
## 用法
```tsx
import { TimeField } from '@heroui/react';
```
```tsx
"use client";
import {Label, TimeField} from "@heroui/react";
export function Basic() {
return (
时间
{(segment) => }
);
}
```
## 组件结构
```tsx
import {TimeField, Label, Description, FieldError} from '@heroui/react';
export default () => (
{(segment) => }
)
```
> **TimeField** 将标签、时间输入、说明与错误信息组合为单个无障碍组件。
## 示例
### 带图标
添加前缀或后缀图标以增强时间字段。
```tsx
"use client";
import {Clock} from "@gravity-ui/icons";
import {Label, TimeField} from "@heroui/react";
export function WithPrefixIcon() {
return (
时间
{(segment) => }
);
}
```
```tsx
"use client";
import {Clock} from "@gravity-ui/icons";
import {Label, TimeField} from "@heroui/react";
export function WithSuffixIcon() {
return (
时间
{(segment) => }
);
}
```
```tsx
"use client";
import {ChevronDown, Clock} from "@gravity-ui/icons";
import {Description, Label, TimeField} from "@heroui/react";
export function WithPrefixAndSuffix() {
return (
时间
{(segment) => }
输入时间
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请在 `TimeField.Group` 上使用 `variant="secondary"`,以应用适合表面背景的低强调变体。
```tsx
"use client";
import {Clock} from "@gravity-ui/icons";
import {Description, Label, Surface, TimeField} from "@heroui/react";
export function OnSurface() {
return (
时间
{(segment) => }
输入时间
预约时间
{(segment) => }
输入预约时间
);
}
```
### 带描述
```tsx
"use client";
import {Description, Label, TimeField} from "@heroui/react";
export function WithDescription() {
return (
开始时间
{(segment) => }
输入开始时间
结束时间
{(segment) => }
输入结束时间
);
}
```
### 必填字段
```tsx
"use client";
import {Description, Label, TimeField} from "@heroui/react";
export function Required() {
return (
时间
{(segment) => }
预约时间
{(segment) => }
必填项
);
}
```
### 禁用
```tsx
"use client";
import {Description, Label, TimeField} from "@heroui/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 (
时间
{(segment) => }
此时间字段已禁用
时间
{(segment) => }
此时间字段已禁用
);
}
```
### 宽度充满
```tsx
"use client";
import {ChevronDown, Clock} from "@gravity-ui/icons";
import {Label, TimeField} from "@heroui/react";
export function FullWidth() {
return (
时间
{(segment) => }
时间
{(segment) => }
);
}
```
### 表单校验
将 `isInvalid` 与 `FieldError` 配合使用,以展示校验消息。
```tsx
"use client";
import {FieldError, Label, TimeField} from "@heroui/react";
export function Invalid() {
return (
时间
{(segment) => }
请输入有效时间
时间
{(segment) => }
时间须在工作时间内
);
}
```
### 受控组件
控制 value 以与其他组件或状态管理同步。
```tsx
"use client";
import type {TimeValue} from "@heroui/react";
import {Button, Description, Label, TimeField} from "@heroui/react";
import {Time, getLocalTimeZone, now} from "@internationalized/date";
import {useState} from "react";
export function Controlled() {
const [value, setValue] = useState(null);
return (
时间
{(segment) => }
当前值:{value ? value.toString() : "(空)"}
{
const currentTime = now(getLocalTimeZone());
setValue(new Time(currentTime.hour, currentTime.minute, currentTime.second));
}}
>
设为当前时间
setValue(null)}>
清空
);
}
```
### 表单示例
包含校验与提交处理的完整表单示例。
```tsx
"use client";
import type {Time} from "@internationalized/date";
import {Clock} from "@gravity-ui/icons";
import {Button, Description, FieldError, Form, Label, TimeField} from "@heroui/react";
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.SyntheticEvent) => {
e.preventDefault();
if (!value || isInvalid) {
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
console.log("Time submitted:", {time: value});
setValue(null);
setIsSubmitting(false);
}, 1500);
};
return (
预约时间
{(segment) => }
{isInvalid ? (
时间须在上午 9:00 至下午 5:00 之间
) : (
输入上午 9:00 至下午 5:00 之间的时间
)}
{isSubmitting ? "提交中…" : "提交"}
);
}
```
### 带校验
TimeField 支持通过 `minValue`、`maxValue` 及自定义校验逻辑进行验证。
```tsx
"use client";
import type {Time} from "@internationalized/date";
import {Description, FieldError, Label, TimeField} from "@heroui/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 (
时间
{(segment) => }
{isInvalid ? (
时间须在上午 9:00 至下午 5:00 之间
) : (
输入上午 9:00 至下午 5:00 之间的时间
)}
);
}
```
### 渲染函数
```tsx
"use client";
import {Label, TimeField} from "@heroui/react";
export function RenderFunction() {
return (
}
>
时间
{(segment) => }
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Description, Label, TimeField} from "@heroui/react";
export function CustomStyles() {
return (
提醒时间
每日签到通知。
{(segment) => }
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```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;
}
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
* `.time-field` – 轻量样式的根容器(`flex flex-col gap-1`)
> **说明:** 子组件([Label](/docs/react/components/label)、[Description](/docs/react/components/description)、[FieldError](/docs/react/components/field-error))拥有各自的 CSS 类与样式。自定义方式请参阅对应文档。`TimeField.Group` 的样式见下文 API 参考。
### 交互状态
TimeField 会根据状态自动设置以下 data 属性:
* **无效**:`[data-invalid="true"]` 或 `[aria-invalid="true"]` – 无效时自动隐藏 description 插槽
* **必填**:`[data-required="true"]` – 当 `isRequired` 为 true 时添加
* **禁用**:`[data-disabled="true"]` – 当 `isDisabled` 为 true 时添加
* **焦点在内**:`[data-focus-within="true"]` – 任一子输入聚焦时添加
## API 参考
### TimeField
TimeField 继承 React Aria [TimeField](https://react-aria.adobe.com/TimeField) 的全部 props。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------ | ------- | ---------------------------------- |
| `children` | `React.ReactNode \| (values: TimeFieldRenderProps) => React.ReactNode` | - | 子组件(Label、TimeField.Group 等)或渲染函数。 |
| `className` | `string \| (values: TimeFieldRenderProps) => string` | - | 用于样式的 CSS 类,支持渲染 prop。 |
| `style` | `React.CSSProperties \| (values: TimeFieldRenderProps) => React.CSSProperties` | - | 内联样式,支持渲染 prop。 |
| `fullWidth` | `boolean` | `false` | 时间字段是否占满容器宽度。 |
| `id` | `string` | - | 元素的唯一 id。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
#### Value Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | ------------------------------------ | --- | ----------------------------------------------------------------------------------------------- |
| `value` | `TimeValue \| null` | - | 当前值(受控)。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `defaultValue` | `TimeValue \| null` | - | 默认值(非受控)。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `onChange` | `(value: TimeValue \| null) => void` | - | 值变化时触发的事件处理函数。 |
| `placeholderValue` | `TimeValue \| null` | - | 影响占位符格式的占位时间;默认随小时制为 12:00 AM 或 00:00。 |
#### Validation Props
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | -------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------ |
| `isRequired` | `boolean` | `false` | 是否在提交表单前要求用户输入。 |
| `isInvalid` | `boolean` | - | 值是否无效。 |
| `minValue` | `TimeValue \| null` | - | 用户可选择最早时间。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `maxValue` | `TimeValue \| null` | - | 用户可选择最晚时间。类型见 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/)。 |
| `validate` | `(value: TimeValue) => ValidationError \| true \| null \| undefined` | - | 自定义校验函数。 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 表单校验还是 ARIA 属性。 |
#### Format Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------------- | -------------------------------- | ---------- | ---------------------------- |
| `granularity` | `'hour' \| 'minute' \| 'second'` | `'minute'` | 时间选择器显示的最小单位。 |
| `hourCycle` | `12 \| 24` | - | 以 12 或 24 小时制显示时间;默认由语言环境决定。 |
| `hideTimeZone` | `boolean` | `false` | 是否隐藏时区缩写。 |
| `shouldForceLeadingZeros` | `boolean` | - | 是否始终为小时字段显示前导零;默认由语言环境决定。 |
#### State Props
| Prop | 类型 | 默认值 | 描述 |
| ------------ | --------- | --- | ----------- |
| `isDisabled` | `boolean` | - | 是否禁用输入。 |
| `isReadOnly` | `boolean` | - | 是否可选中但不可修改。 |
#### Form Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------- | --- | ----------------------------------------- |
| `name` | `string` | - | 输入元素的 name,用于 HTML 表单提交;以 ISO 8601 字符串提交。 |
| `autoFocus` | `boolean` | - | 是否在渲染后自动聚焦该元素。 |
#### Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------- | --- | ------------- |
| `aria-label` | `string` | - | 无可见标签时的无障碍标签。 |
| `aria-labelledby` | `string` | - | 标注该字段的元素 id。 |
| `aria-describedby` | `string` | - | 描述该字段的元素 id。 |
| `aria-details` | `string` | - | 包含额外详情的元素 id。 |
### 组合组件
TimeField 与以下独立组件配合使用,请分别导入并直接使用:
* **Label** – 来自 `@heroui/react` 的字段标签
* **TimeField.Group** – 时间输入分组(详见下文)
* **TimeField.Input** – 来自 `@heroui/react` 的分段位编辑输入
* **TimeField.Segment** – 单个时间段位(时、分、秒等)
* **TimeField.Prefix** / **TimeField.Suffix** – 输入组的前缀与后缀插槽
* **Description** – 来自 `@heroui/react` 的辅助说明
* **FieldError** – 来自 `@heroui/react` 的校验错误信息
这些组件各自有独立的 props API。在 TimeField 中直接组合使用:
```tsx
import {parseTime} from '@internationalized/date';
import {TimeField, Label, Description, FieldError} from '@heroui/react';
Appointment Time
{(segment) => }
Select a time between 9:00 AM and 5:00 PM.
Please select a valid time.
```
### TimeValue 类型
TimeField 使用 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) 中的类型:
* `Time` – 仅时间(时、分、秒)
* `CalendarDateTime` – 含日期与时间、不含时区(TimeField 仅展示时间部分)
* `ZonedDateTime` – 含日期、时间与时区(TimeField 仅展示时间部分)
示例:
```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
{/* ... */}
```
> **说明:** TimeField 使用 [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) 进行时间处理、解析与类型定义。更多类型与函数见 [Internationalized Date 文档](https://react-aria.adobe.com/internationalized/date/)。
### TimeFieldRenderProps
对 `className`、`style` 或 `children` 使用渲染 prop 时,可使用以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | ------------- |
| `isDisabled` | `boolean` | 字段是否禁用。 |
| `isInvalid` | `boolean` | 字段当前是否无效。 |
| `isReadOnly` | `boolean` | 字段是否只读。 |
| `isRequired` | `boolean` | 字段是否必填。 |
| `isFocused` | `boolean` | 字段是否聚焦。 |
| `isFocusWithin` | `boolean` | 是否有子元素聚焦。 |
| `isFocusVisible` | `boolean` | 焦点是否可见(键盘导航)。 |
### TimeField.Group
TimeField.Group 继承 React Aria `Group` 的全部 props,并额外支持:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------- | ----------- | ---------------------------------------------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调、无阴影,适合用于 Surface。 |
### TimeField.Input
TimeField.Input 继承 React Aria `DateInput` 的全部 props,并额外支持:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------- | ----------- | ------------------------------------------------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 输入的视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调、无阴影,适合用于 Surface。 |
`TimeField.Input` 接受渲染函数作为子节点,函数接收日期段位;每个段位表示时间的一部分(时、分、秒等)。
### TimeField.Segment
TimeField.Segment 继承 React Aria `DateSegment` 的全部 props:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------- | --- | ------------------------------------------ |
| `segment` | `DateSegment` | - | 来自 TimeField.Input 渲染函数的 `DateSegment` 对象。 |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
### TimeField.Prefix
TimeField.Prefix 接受标准 HTML `div` 属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------------ |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 前缀插槽中要显示的内容。 |
### TimeField.Suffix
TimeField.Suffix 接受标准 HTML `div` 属性:
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------------ |
| `className` | `string` | - | 与组件样式合并的 Tailwind CSS 类。 |
| `children` | `ReactNode` | - | 后缀插槽中要显示的内容。 |
## TimeField.Group 样式
### 全局 CSS
基础类作用于所有实例,可通过 `@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 类
* `.date-input-group` – 根容器样式
* `.date-input-group__input` – 输入包裹层样式
* `.date-input-group__segment` – 单个时间段位样式
* `.date-input-group__prefix` – 前缀元素样式
* `.date-input-group__suffix` – 后缀元素样式
### TimeField.Group 交互状态
* **悬停**:`:hover` 或 `[data-hovered="true"]`
* **焦点在内**:`[data-focus-within="true"]` 或 `:focus-within`
* **无效**:`[data-invalid="true"]`(同时与 `aria-invalid` 同步)
* **禁用**:`[data-disabled="true"]` 或 `[aria-disabled="true"]`
* **段位聚焦**:段位上的 `:focus` 或 `[data-focused="true"]`
* **段位占位符**:段位上的 `[data-placeholder="true"]`
## 相关组件
## Related Components
* **Label**: Accessible label for form controls
* **FieldError**: Inline validation messages for form fields
* **Description**: Helper text for form fields
# Alert 警告
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/alert
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(feedback)/alert.mdx
> 向用户展示重要消息与通知,并提供状态指示
## 用法
```tsx
import { Alert } from '@heroui/react';
```
```tsx
import {Alert, Button, CloseButton, Spinner} from "@heroui/react";
import React from "react";
export function Basic() {
return (
{/* 默认 — 一般信息 */}
新功能已上线
查看我们的最新更新,包括深色模式支持与改进的无障碍体验。
{/* 强调 — 重要信息含操作 */}
有可用更新
应用有新版本可用。请刷新页面以获取最新功能与问题修复。
刷新
刷新
{/* 危险 — 错误与排查步骤 */}
无法连接到服务器
当前遇到连接问题,请尝试以下操作:
重试
重试
{/* 无描述 */}
个人资料已更新
{/* 自定义指示器 — 加载中 */}
正在处理你的请求
正在同步你的数据,请稍候,这可能需要一点时间。
{/* 无关闭按钮 */}
计划维护
我们将于 UTC 时间 3 月 15 日(周日)凌晨 2:00 至上午 6:00
进行计划维护,期间服务将暂时不可用。
);
}
```
## 组件结构
```tsx
import { Alert } from '@heroui/react';
export default () => (
)
```
## 自定义样式
### Tailwind CSS
```tsx
import {Alert, Button, CloseButton} from "@heroui/react";
export function CustomStyles() {
return (
支付方式即将过期
您的 Visa 卡(尾号 4242)将于 3 月 28 日过期。请更新账单信息,以免影响 Pro 订阅。
更新账单
更新账单
);
}
```
### 全局 CSS
要自定义 Alert 的组件类,可使用 `@layer components` 指令。
[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Alert 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/alert.css)):
#### 基础类 \[!toc]
* `.alert` - Alert 根容器
* `.alert__indicator` - 图标/指示器容器
* `.alert__content` - 包裹标题与说明的内容容器
* `.alert__title` - Alert 标题文本
* `.alert__description` - Alert 说明文本
#### 状态变体类 \[!toc]
* `.alert--default` - 默认灰色状态
* `.alert--accent` - 强调蓝色状态
* `.alert--success` - 成功绿色状态
* `.alert--warning` - 警告黄/橙色状态
* `.alert--danger` - 危险红色状态
### 交互状态
Alert 主要用于信息展示,基础组件本身通常没有交互状态;但它可以包含按钮或关闭按钮等交互元素。
## API 参考
### Alert
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | ----------- | ----------- |
| `status` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Alert 的视觉状态 |
| `className` | `string` | - | 附加的 CSS 类 |
| `children` | `ReactNode` | - | Alert 内容 |
### Alert.Indicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ----------------- |
| `className` | `string` | - | 附加的 CSS 类 |
| `children` | `ReactNode` | - | 自定义指示图标(默认显示状态图标) |
### Alert.Content
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------------------------- |
| `className` | `string` | - | 附加的 CSS 类 |
| `children` | `ReactNode` | - | 内容(通常为 Title 与 Description) |
### Alert.Title
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ---------- |
| `className` | `string` | - | 附加的 CSS 类 |
| `children` | `ReactNode` | - | Alert 标题文本 |
### Alert.Description
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ---------- |
| `className` | `string` | - | 附加的 CSS 类 |
| `children` | `ReactNode` | - | Alert 说明文本 |
## 相关组件
## Related Components
* **CloseButton**: Button for dismissing overlays
* **Button**: Allows a user to perform an action
* **Spinner**: Loading indicator
# Meter 计量条
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/meter
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(feedback)/meter.mdx
> Meter 表示已知范围内的数量,或一个比例值。
## 用法
```tsx
import { Meter, Label } from '@heroui/react';
```
```tsx
import {Label, Meter} from "@heroui/react";
export function Basic() {
return (
存储空间
);
}
```
## 组件结构
```tsx
import { Meter, Label } from '@heroui/react';
export default () => (
Storage
);
```
## 示例
### 尺寸
```tsx
import {Label, Meter} from "@heroui/react";
const SIZE_LABELS = {
lg: "大",
md: "中",
sm: "小",
} as const;
export function Sizes() {
return (
{SIZE_LABELS.sm}
{SIZE_LABELS.md}
{SIZE_LABELS.lg}
);
}
```
### 颜色
```tsx
import {Label, Meter} from "@heroui/react";
const colors = ["default", "accent", "success", "warning", "danger"] as const;
const COLOR_LABELS: Record<(typeof colors)[number], string> = {
accent: "强调",
danger: "危险",
default: "默认",
success: "成功",
warning: "警告",
};
export function Colors() {
return (
{colors.map((color) => (
{COLOR_LABELS[color]}
))}
);
}
```
### 无可见标签
无需可见标签时,使用 `aria-label` 提供无障碍标签。
```tsx
import {Meter} from "@heroui/react";
export function WithoutLabel() {
return (
);
}
```
### 自定义取值范围与格式
使用 `minValue`、`maxValue` 与 `formatOptions` 自定义取值范围与显示格式。
```tsx
import {Label, Meter} from "@heroui/react";
export function CustomValue() {
return (
收入
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Label, Meter} from "@heroui/react";
export function CustomStyles() {
return (
已用存储空间
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Meter 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/meter.css)):
#### 基础与元素类 \[!toc]
* `.meter` — 基础容器(grid 布局)
* `.meter__output` — 数值文本展示
* `.meter__track` — 轨道背景
* `.meter__fill` — 轨道已填充部分
#### 尺寸类 \[!toc]
* `.meter--sm` — 小尺寸变体(更细的轨道)
* `.meter--md` — 中等尺寸变体(默认)
* `.meter--lg` — 大尺寸变体(更粗的轨道)
#### 颜色类 \[!toc]
* `.meter--default` — 默认颜色变体
* `.meter--accent` — 强调色变体
* `.meter--success` — 成功色变体
* `.meter--warning` — 警告色变体
* `.meter--danger` — 危险色变体
## API 参考
### Meter
继承自 [React Aria Meter](https://react-spectrum.adobe.com/react-aria/Meter.html)。
| Prop | 类型 | 默认值 | 描述 |
| --------------- | ------------------------------------------------------------- | -------------------- | ---------- |
| `value` | `number` | `0` | 当前值 |
| `minValue` | `number` | `0` | 最小值 |
| `maxValue` | `number` | `100` | 最大值 |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Meter 轨道尺寸 |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | 填充条颜色 |
| `formatOptions` | `Intl.NumberFormatOptions` | `{style: 'percent'}` | 数值展示的格式化选项 |
| `valueLabel` | `ReactNode` | - | 自定义数值标签内容 |
| `children` | `ReactNode \| (values: MeterRenderProps) => ReactNode` | - | 内容或渲染 prop |
### MeterRenderProps
使用渲染 prop 模式时,会提供以下值:
| Prop | 类型 | 描述 |
| ------------ | -------- | ---------------- |
| `percentage` | `number` | Meter 百分比(0–100) |
| `valueText` | `string` | 格式化后的数值文本 |
## 相关组件
# ProgressBar 进度条
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/progress-bar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(feedback)/progress-bar.mdx
> 进度条用于展示某项操作随时间变化的确定或不确定进度。
## 用法
```tsx
import { ProgressBar, Label } from '@heroui/react';
```
```tsx
import {Label, ProgressBar} from "@heroui/react";
export function Basic() {
return (
加载中
);
}
```
## 组件结构
```tsx
import { ProgressBar, Label } from '@heroui/react';
export default () => (
Loading
);
```
## 示例
### 尺寸
```tsx
import {Label, ProgressBar} from "@heroui/react";
const SIZE_LABELS = {
lg: "大",
md: "中",
sm: "小",
} as const;
export function Sizes() {
return (
{SIZE_LABELS.sm}
{SIZE_LABELS.md}
{SIZE_LABELS.lg}
);
}
```
### 颜色
```tsx
import {Label, ProgressBar} from "@heroui/react";
const colors = ["default", "accent", "success", "warning", "danger"] as const;
const COLOR_LABELS: Record<(typeof colors)[number], string> = {
accent: "强调",
danger: "危险",
default: "默认",
success: "成功",
warning: "警告",
};
export function Colors() {
return (
{colors.map((color) => (
{COLOR_LABELS[color]}
))}
);
}
```
### 无可见标签
无需可见标签时,使用 `aria-label` 提供无障碍标签。
```tsx
import {ProgressBar} from "@heroui/react";
export function WithoutLabel() {
return (
);
}
```
### 不确定进度
无法确定进度时使用 `isIndeterminate`。
```tsx
import {Label, ProgressBar} from "@heroui/react";
export function Indeterminate() {
return (
加载中…
);
}
```
### 自定义数值范围
使用 `minValue`、`maxValue` 与 `formatOptions` 自定义取值范围与显示格式。
```tsx
"use client";
import {Label, ListBox, NumberField, ProgressBar, Select, Separator} from "@heroui/react";
import {useState} from "react";
const formatStyleOptions: {label: string; value: string}[] = [
{label: "货币", value: "currency"},
{label: "百分比", value: "percent"},
{label: "小数", value: "decimal"},
{label: "单位", 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 (
选项
setValue(v)}
>
值
{
setMinValue(v);
if (value < v) setValue(v);
}}
>
最小值
{
setMaxValue(v);
if (value > v) setValue(v);
}}
>
最大值
setFormat(key as string)}>
格式
{formatStyleOptions.map((option) => (
{option.label}
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Label, ProgressBar} from "@heroui/react";
export function CustomStyles() {
return (
正在上传 resume.pdf
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ProgressBar 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/progress-bar.css)):
#### 基础与元素类 \[!toc]
* `.progress-bar` - 基础容器(网格布局)
* `.progress-bar__output` - 数值文本展示
* `.progress-bar__track` - 轨道背景
* `.progress-bar__fill` - 轨道上已填充部分
#### 尺寸类 \[!toc]
* `.progress-bar--sm` - 小尺寸变体(更细的轨道)
* `.progress-bar--md` - 中等尺寸变体(默认)
* `.progress-bar--lg` - 大尺寸变体(更粗的轨道)
#### 颜色类 \[!toc]
* `.progress-bar--default` - 默认颜色变体
* `.progress-bar--accent` - 强调色变体
* `.progress-bar--success` - 成功色变体
* `.progress-bar--warning` - 警告色变体
* `.progress-bar--danger` - 危险色变体
## API 参考
### ProgressBar
继承自 [React Aria ProgressBar](https://react-spectrum.adobe.com/react-aria/ProgressBar.html)。
| Prop | 类型 | 默认值 | 描述 |
| ----------------- | ------------------------------------------------------------- | -------------------- | ---------- |
| `value` | `number` | `0` | 当前值 |
| `minValue` | `number` | `0` | 最小值 |
| `maxValue` | `number` | `100` | 最大值 |
| `isIndeterminate` | `boolean` | `false` | 是否为不确定进度 |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 进度轨道尺寸 |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | 填充条颜色 |
| `formatOptions` | `Intl.NumberFormatOptions` | `{style: 'percent'}` | 数值展示的数字格式 |
| `valueLabel` | `ReactNode` | - | 自定义数值标签内容 |
| `children` | `ReactNode \| (values: ProgressBarRenderProps) => ReactNode` | - | 内容或渲染 prop |
### ProgressBarRenderProps
使用渲染 prop 模式时,会提供以下值:
| Prop | 类型 | 描述 |
| ----------------- | --------- | ------------ |
| `percentage` | `number` | 进度百分比(0–100) |
| `valueText` | `string` | 格式化后的数值文本 |
| `isIndeterminate` | `boolean` | 是否为不确定进度 |
## 相关组件
# ProgressCircle 环形进度条
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/progress-circle
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(feedback)/progress-circle.mdx
> 环形进度指示器,用于展示确定或不确定的进度。
## 用法
```tsx
import { ProgressCircle } from '@heroui/react';
```
```tsx
import {ProgressCircle} from "@heroui/react";
export function Basic() {
return (
);
}
```
## 组件结构
```tsx
import { ProgressCircle } from '@heroui/react';
export default () => (
);
```
## 示例
### 尺寸
```tsx
import {ProgressCircle} from "@heroui/react";
const SIZE_LABELS = {
lg: "大",
md: "中",
sm: "小",
} as const;
export function Sizes() {
return (
);
}
```
### 颜色
```tsx
import {ProgressCircle} from "@heroui/react";
const colors = ["default", "accent", "success", "warning", "danger"] as const;
const COLOR_LABELS: Record<(typeof colors)[number], string> = {
accent: "强调",
danger: "危险",
default: "默认",
success: "成功",
warning: "警告",
};
export function Colors() {
return (
{colors.map((color) => (
))}
);
}
```
### 不确定进度
无法确定进度时使用 `isIndeterminate`。
```tsx
import {ProgressCircle} from "@heroui/react";
export function Indeterminate() {
return (
);
}
```
### 带标签
```tsx
import {Label, ProgressCircle} from "@heroui/react";
export function WithLabel() {
return (
);
}
```
### 自定义 SVG 属性
各子部分均为可组合组件,可直接覆盖 `strokeWidth`、`r`、`cx`、`cy`、`viewBox` 等 SVG 属性。
```tsx
import {ProgressCircle} from "@heroui/react";
export function CustomSvg() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ProgressCircle} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ProgressCircle 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/progress-circle.css)):
#### 基础与元素类 \[!toc]
* `.progress-circle` - 基础容器
* `.progress-circle__track` - SVG 元素
* `.progress-circle__track-circle` - 背景圆环
* `.progress-circle__fill-circle` - 进度弧
#### 尺寸类 \[!toc]
* `.progress-circle--sm` - 小尺寸
* `.progress-circle--md` - 中等尺寸(默认)
* `.progress-circle--lg` - 大尺寸
#### 颜色类 \[!toc]
* `.progress-circle--default` - 默认颜色
* `.progress-circle--accent` - 强调色
* `.progress-circle--success` - 成功色
* `.progress-circle--warning` - 警告色
* `.progress-circle--danger` - 危险色
## API 参考
### ProgressCircle
继承自 [React Aria ProgressBar](https://react-spectrum.adobe.com/react-aria/ProgressBar.html)。
| Prop | 类型 | 默认值 | 描述 |
| ----------------- | ------------------------------------------------------------- | -------------------- | ---------- |
| `value` | `number` | `0` | 当前值 |
| `minValue` | `number` | `0` | 最小值 |
| `maxValue` | `number` | `100` | 最大值 |
| `isIndeterminate` | `boolean` | `false` | 是否为不确定进度 |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 圆环尺寸 |
| `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | 进度弧颜色 |
| `formatOptions` | `Intl.NumberFormatOptions` | `{style: 'percent'}` | 数值展示格式 |
| `children` | `ReactNode \| (values: ProgressBarRenderProps) => ReactNode` | - | 内容或渲染 prop |
### ProgressBarRenderProps
使用渲染 prop 模式时,会提供以下值:
| Prop | 类型 | 描述 |
| ----------------- | --------- | ------------ |
| `percentage` | `number` | 进度百分比(0–100) |
| `valueText` | `string` | 格式化后的数值文案 |
| `isIndeterminate` | `boolean` | 是否为不确定进度 |
## 相关组件
# Skeleton 骨架屏
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/skeleton
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(feedback)/skeleton.mdx
> Skeleton 用于展示加载状态,并预览组件的预期形状。
## 用法
```tsx
import { Skeleton } from '@heroui/react';
```
```tsx
import {Skeleton} from "@heroui/react";
export function Basic() {
return (
);
}
```
## 示例
### 文本内容
```tsx
import {Skeleton} from "@heroui/react";
export function TextContent() {
return (
);
}
```
### 用户资料
```tsx
import {Skeleton} from "@heroui/react";
export function UserProfile() {
return (
);
}
```
### 列表项
```tsx
import {Skeleton} from "@heroui/react";
export function List() {
return (
{Array.from({length: 3}).map((_, index) => (
))}
);
}
```
### 网格
```tsx
import {Skeleton} from "@heroui/react";
export function Grid() {
return (
);
}
```
### 单次闪烁
同步的 shimmer 效果会一次性扫过所有骨架元素。在父容器上应用 `skeleton--shimmer` 类,并将子骨架的 `animationType` 设为 `"none"`。
```tsx
import {Skeleton} from "@heroui/react";
export function SingleShimmer() {
return (
);
}
```
### 动画类型
```tsx
import {Skeleton} from "@heroui/react";
export function AnimationTypes() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Skeleton} from "@heroui/react";
const bone = "animate-shine rounded-lg bg-neutral-200/90 dark:bg-neutral-800/90";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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 */
}
}
```
### 全局动画配置
可在全局 CSS 中通过 `--skeleton-animation` 变量为所有 Skeleton 组件设置默认动画类型:
```css
/* 全局 CSS 文件 */
:root {
/* 可选值:shimmer、pulse、none */
--skeleton-animation: pulse;
}
/* 也可为浅色/深色主题设置不同值 */
.light, [data-theme="light"] {
--skeleton-animation: shimmer;
}
.dark, [data-theme="dark"] {
--skeleton-animation: pulse;
}
```
在单个组件上指定 `animationType` 属性会覆盖此全局设置。
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### 全局动画配置
你可以通过在应用中定义 `--skeleton-animation` CSS 变量,为所有 Skeleton 设置默认动画类型:
```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;
}
```
在单个组件上指定 `animationType` 时,会覆盖上述全局设置。
### CSS 类
Skeleton 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/skeleton.css)):
#### 基础类 \[!toc]
`.skeleton` - 包含背景与圆角等基础骨架样式
#### 动画变体类 \[!toc]
* `.skeleton--shimmer` - 添加带渐变效果的闪烁动画(默认)
* `.skeleton--pulse` - 使用 Tailwind 的 `animate-pulse` 添加脉冲动画
* `.skeleton--none` - 无动画的静态骨架
### 动画
Skeleton 支持三种动画类型,视觉效果各不相同:
#### 闪烁动画 \[!toc]
闪烁效果会在骨架元素上移动渐变:
```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-[''];
}
```
闪烁动画在主题中通过以下方式定义:
```css
@theme inline {
--animate-skeleton: skeleton 2s linear infinite;
@keyframes skeleton {
100% {
transform: translateX(200%);
}
}
}
```
#### 脉冲动画 \[!toc]
脉冲动画使用 Tailwind 内置的 `animate-pulse` 工具类:
```css
.skeleton--pulse {
@apply animate-pulse;
}
```
#### 无动画 \[!toc]
用于不需要任何动画的静态骨架:
```css
.skeleton--none {
/* No animation styles applied */
}
```
## API 参考
### Skeleton
| Prop | 类型 | 默认值 | 描述 |
| --------------- | -------------------------------- | -------------------- | ------------------------------------------------------- |
| `animationType` | `"shimmer" \| "pulse" \| "none"` | `"shimmer"` 或 CSS 变量 | Skeleton 的动画类型;也可通过 `--skeleton-animation` CSS 变量进行全局配置 |
| `className` | `string` | - | 额外的 CSS 类名 |
## 相关组件
## Related Components
* **Card**: Content container with header, body, and footer
* **Avatar**: Display user profile images
# Spinner 加载指示器
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/spinner
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(feedback)/spinner.mdx
> 用于展示等待或处理中状态的加载指示组件。
## 用法
```tsx
import { Spinner } from '@heroui/react';
```
```tsx
import {Spinner} from "@heroui/react";
export function SpinnerBasic() {
return (
);
}
```
## 示例
### 颜色
```tsx
import {Spinner} from "@heroui/react";
const COLOR_LABELS = {
accent: "强调",
current: "当前",
danger: "危险",
success: "成功",
warning: "警告",
} as const;
const colors = ["current", "accent", "success", "warning", "danger"] as const;
export function SpinnerColors() {
return (
{colors.map((color) => (
{COLOR_LABELS[color]}
))}
);
}
```
### 尺寸
```tsx
import {Spinner} from "@heroui/react";
const SIZE_LABELS = {
lg: "大",
md: "中",
sm: "小",
xl: "特大",
} as const;
const sizes = ["sm", "md", "lg", "xl"] as const;
export function SpinnerSizes() {
return (
{sizes.map((size) => (
{SIZE_LABELS[size]}
))}
);
}
```
### 速度
为单个 Spinner 添加动画工具类即可调整其旋转速度。请同时加上 `motion-reduce:animate-none`,以便在用户开启“减少动态效果”时保持静止。
```tsx
import {Spinner} from "@heroui/react";
export function SpinnerSpeed() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Spinner} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.spinner {
@apply animate-spin;
}
.spinner--accent {
color: var(--accent);
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Spinner 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/spinner.css)):
#### 基础类与尺寸类 \[!toc]
* `.spinner` - 基础样式与默认尺寸
* `.spinner--sm` - 小尺寸变体
* `.spinner--md` - 中等尺寸变体(默认)
* `.spinner--lg` - 大尺寸变体
* `.spinner--xl` - 特大尺寸变体
#### 颜色类 \[!toc]
* `.spinner--current` - 继承当前文本颜色
* `.spinner--accent` - 强调色变体
* `.spinner--danger` - 危险色变体
* `.spinner--success` - 成功色变体
* `.spinner--warning` - 警告色变体
## API 参考
### Spinner
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | ---------- | ------------- |
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Spinner 的尺寸 |
| `color` | `"current" \| "accent" \| "success" \| "warning" \| "danger"` | `"accent"` | Spinner 的颜色变体 |
| `className` | `string` | - | 额外的 CSS 类名 |
## 相关组件
# CheckboxGroup 复选框组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/checkbox-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/checkbox-group.mdx
> 用于管理多个复选框选择的复选框组组件
## 用法
```tsx
import { CheckboxGroup, Checkbox, Label, Description } from '@heroui/react';
```
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
export function Basic() {
return (
选择你的兴趣
可多选
编程
热爱构建软件
设计
喜欢打造精美界面
写作
热衷于内容创作
);
}
```
## 组件结构
```tsx
import {CheckboxGroup, Checkbox, Label, Description, FieldError} from '@heroui/react';
export default () => (
{/* 可选 */}
Label {/* 纯文本 — 可点击标签 */}
{/* 可选 — 单个 Checkbox 的帮助文本 */}
{/* 可选 */}
);
```
## 示例
### 表面样式
在 [Surface](/docs/react/components/surface) 组件内使用时,请使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
import {Checkbox, CheckboxGroup, Description, Label, Surface} from "@heroui/react";
export function OnSurface() {
return (
选择你的兴趣
可多选
编程
热爱构建软件
设计
喜欢打造精美界面
写作
热衷于内容创作
);
}
```
### 禁用
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
export function Disabled() {
return (
功能
功能选择暂时不可用
功能一
该功能即将推出
功能二
该功能即将推出
);
}
```
### 半选状态
```tsx
"use client";
import {Checkbox, CheckboxGroup} from "@heroui/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 : []);
}}
>
全选
编程
设计
写作
);
}
```
### 受控组件
```tsx
"use client";
import {Checkbox, CheckboxGroup, Label} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [selected, setSelected] = useState(["coding", "design"]);
return (
你的技能
编程
设计
写作
已选:{selected.join(", ") || "无"}
);
}
```
### 表单校验
```tsx
"use client";
import {Button, Checkbox, CheckboxGroup, FieldError, Form, Label} from "@heroui/react";
export function Validation() {
return (
{
e.preventDefault();
const formData = new FormData(e.currentTarget);
const values = formData.getAll("preferences");
alert(`已选偏好:${values.join(", ")}`);
}}
>
偏好设置
邮件通知
短信通知
推送通知
请至少选择一种通知方式。
提交
);
}
```
### 功能扩展示例
```tsx
import {Bell, Comment, Envelope} from "@gravity-ui/icons";
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
import clsx from "clsx";
export function FeaturesAndAddOns() {
const addOns = [
{
description: "通过邮件接收更新",
icon: Envelope,
title: "邮件通知",
value: "email",
},
{
description: "即时短信通知",
icon: Comment,
title: "短信提醒",
value: "sms",
},
{
description: "浏览器与移动端推送提醒",
icon: Bell,
title: "推送通知",
value: "push",
},
];
return (
通知偏好
选择接收更新的方式
{addOns.map((addon) => (
{addon.title}
{addon.description}
))}
);
}
```
### 自定义指示器
```tsx
"use client";
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
export function WithCustomIndicator() {
return (
功能
选择你需要的功能
{({isSelected}) =>
isSelected ? (
) : null
}
邮件通知
通过邮件接收更新
{({isSelected}) =>
isSelected ? (
) : null
}
邮件通讯
每周接收邮件简报
);
}
```
### 渲染函数
```tsx
"use client";
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
export function RenderFunction() {
return (
}>
选择你的兴趣
可多选
编程
热爱构建软件
设计
喜欢打造精美界面
写作
热衷于内容创作
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Checkbox, CheckboxGroup, Description, Label} from "@heroui/react";
const controlClassName = "bg-success-soft before:bg-success";
const indicatorClassName =
"**:data-[slot=checkbox-default-indicator--checkmark]:text-success-foreground";
const channels = [
{label: "电子邮件", value: "email"},
{label: "短信", value: "sms"},
{label: "推送", value: "push"},
] as const;
export function CustomStyles() {
return (
通知渠道
选择我们通过何种方式向您发送账户更新。
{channels.map(({label, value}) => (
{label}
))}
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 CheckboxGroup 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.checkbox-group {
@apply flex flex-col gap-2;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
CheckboxGroup 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/checkbox-group.css)):
#### 基础类 \[!toc]
* `.checkbox-group` - 复选框组根容器
## API 参考
### CheckboxGroup
继承自 [React Aria CheckboxGroup](https://react-spectrum.adobe.com/react-aria/CheckboxGroup.html)。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | -------------------------------------------------------------------------------- | ------- | -------------------------- |
| `value` | `string[]` | - | 当前选中值(受控) |
| `defaultValue` | `string[]` | - | 默认选中值(非受控) |
| `onChange` | `(value: string[]) => void` | - | 选中值变化时的回调 |
| `isDisabled` | `boolean` | `false` | 是否禁用 |
| `isRequired` | `boolean` | `false` | 是否必填 |
| `isReadOnly` | `boolean` | `false` | 是否只读 |
| `isInvalid` | `boolean` | `false` | 是否处于无效状态 |
| `name` | `string` | - | 提交 HTML 表单时复选框组的名称 |
| `children` | `React.ReactNode \| (values: CheckboxGroupRenderProps) => React.ReactNode` | - | 内容或 render prop |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Render Props
使用 render prop 模式时,提供以下值:
| Prop | 类型 | 描述 |
| ------------ | ---------- | -------- |
| `value` | `string[]` | 当前选中值 |
| `isDisabled` | `boolean` | 是否禁用 |
| `isReadOnly` | `boolean` | 是否只读 |
| `isInvalid` | `boolean` | 是否处于无效状态 |
| `isRequired` | `boolean` | 是否必填 |
## 相关组件
## Related Components
* **Checkbox**: Binary choice input control
* **Label**: Accessible label for form controls
* **Fieldset**: Group related form controls with legends
# Checkbox 复选框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/checkbox
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/checkbox.mdx
> 复选框允许用户从列表中选择多项,或标记单个项目为选中状态
## 用法
```tsx
import { Checkbox } from '@heroui/react';
```
```tsx
import {Checkbox} from "@heroui/react";
export function Basic() {
return (
接受条款与条件
);
}
```
## 组件结构
```tsx
import { Checkbox, Description, FieldError } from '@heroui/react';
export default () => (
Label {/* 纯文本 — 可点击标签与无障碍名称 */}
{/* 可选 — 字段级帮助文本 */}
{/* 可选 — 校验消息 */}
);
```
## 示例
### 变体
Checkbox 组件支持两种视觉变体:
* **`primary`**(默认)- 标准样式与默认背景,适用于大多数场景
* **`secondary`** - 低强调变体,适用于 Surface 组件内
```tsx
import {Checkbox, Description} from "@heroui/react";
export function Variants() {
return (
);
}
```
### 全圆角
```tsx
import {Checkbox, Label} from "@heroui/react";
export function FullRounded() {
return (
);
}
```
### 禁用
```tsx
import {Checkbox, Description} from "@heroui/react";
export function Disabled() {
return (
高级功能
该功能即将推出
);
}
```
### 外部标签
```tsx
import {Checkbox, Label} from "@heroui/react";
export function ExternalLabel() {
return (
给我发送营销邮件
);
}
```
### 带描述
```tsx
import {Checkbox, Description} from "@heroui/react";
export function WithDescription() {
return (
邮件通知
当有人在评论中提及您时收到通知
);
}
```
### 默认选中
```tsx
import {Checkbox} from "@heroui/react";
export function DefaultSelected() {
return (
启用邮件通知
);
}
```
### 无效状态
```tsx
import {Checkbox, FieldError} from "@heroui/react";
export function Invalid() {
return (
我同意条款
您必须接受条款才能继续
);
}
```
### 受控组件
```tsx
"use client";
import {Checkbox} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [isSelected, setIsSelected] = useState(true);
return (
邮件通知
状态:{isSelected ? "已勾选" : "未勾选"}
);
}
```
### 半选状态
```tsx
"use client";
import {Checkbox, Description} from "@heroui/react";
import {useState} from "react";
export function Indeterminate() {
const [isIndeterminate, setIsIndeterminate] = useState(true);
const [isSelected, setIsSelected] = useState(false);
return (
{
setIsSelected(selected);
setIsIndeterminate(false);
}}
>
全选
展示部分选中状态(短横线图标)
);
}
```
### 表单集成
```tsx
"use client";
import {Button, Checkbox} from "@heroui/react";
import React from "react";
export function Form() {
const handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
alert(
`表单提交数据:\n${Array.from(formData.entries())
.map(([key, value]) => `${key}: ${value}`)
.join("\n")}`,
);
};
return (
启用通知
订阅新闻通讯
接收营销更新
提交
);
}
```
### 渲染属性
```tsx
"use client";
import {Checkbox, Description} from "@heroui/react";
export function RenderProps() {
return (
{({isSelected}) => (
<>
{isSelected ? "已同意条款" : "接受条款"}
{isSelected ? "感谢您的确认" : "请先阅读并接受条款"}
>
)}
);
}
```
### 渲染函数
```tsx
"use client";
import {Checkbox} from "@heroui/react";
export function RenderFunction() {
return (
}>
接受条款与条件
);
}
```
### 自定义指示器
```tsx
"use client";
import {Checkbox} from "@heroui/react";
export function CustomIndicator() {
return (
{({isSelected}) =>
isSelected ? (
) : null
}
心形
{({isSelected}) =>
isSelected ? (
) : null
}
加号
{({isIndeterminate}) =>
isIndeterminate ? (
) : null
}
部分选中
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Checkbox} from "@heroui/react";
export function CustomStyles() {
return (
自定义样式复选框
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Checkbox 组件类。
[了解更多](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 items-center gap-3;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Checkbox 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/checkbox.css)):
#### 基础类 \[!toc]
* `.checkbox` - 基础复选框容器(字段)
* `.checkbox__content` - 包裹控件与标签文本的可点击 label
* `.checkbox__control` - 复选框控件框
* `.checkbox__indicator` - 复选框勾选指示器
### 交互状态
复选框同时支持 CSS 伪类与 data 属性:
* **Selected**:`[data-selected="true"]` 或 `[aria-checked="true"]`(显示勾选与背景色变化)
* **Indeterminate**:`[data-indeterminate="true"]`(显示不确定状态的短横线)
* **Invalid**:`[data-invalid="true"]` 或 `[aria-invalid="true"]`(显示 danger 色错误状态)
* **Hover**:`Checkbox.Control`(按钮)上的 `:hover` 或 `[data-hovered="true"]`
* **Focus**:按钮上的 `:focus-visible` 或 `[data-focus-visible="true"]`(控件上显示焦点环)
* **Disabled**:字段上的 `[data-disabled="true"]`(降低透明度,包括帮助文本)
* **Pressed**:`:active` 或 `[data-pressed="true"]`
## API 参考
### Checkbox
继承自 [React Aria CheckboxField](https://react-spectrum.adobe.com/react-aria/Checkbox.html)。
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | -------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------- |
| `isSelected` | `boolean` | `false` | 是否选中 |
| `defaultSelected` | `boolean` | `false` | 默认是否选中(非受控) |
| `isIndeterminate` | `boolean` | `false` | 是否处于不确定状态 |
| `isDisabled` | `boolean` | `false` | 是否禁用 |
| `isInvalid` | `boolean` | `false` | 是否无效 |
| `isReadOnly` | `boolean` | `false` | 是否只读 |
| `isRequired` | `boolean` | `false` | 是否必须选中 |
| `validate` | `(value: boolean) => ValidationError \| true \| null \| undefined` | - | 自定义验证函数 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 表单验证还是 ARIA |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调无阴影,适用于 Surface 内 |
| `name` | `string` | - | 提交 HTML 表单时 input 元素的名称 |
| `value` | `string` | - | 提交 HTML 表单时 input 元素的值 |
| `onChange` | `(isSelected: boolean) => void` | - | 值变化时的回调 |
| `children` | `React.ReactNode \| (values: CheckboxFieldRenderProps) => React.ReactNode` | - | 内容或字段 render prop |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Checkbox.Content
可点击的 ``,包裹控件与标签文本。将 `Checkbox.Control` 与 `Label` 放在其中;`Description`/`FieldError` 作为 `Checkbox.Content` 的兄弟节点。无标签的复选框可省略 `Label`,并在 `Checkbox` 上传递 `aria-label`。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------------------------------------------------------------------- | --- | ---------------------------- |
| `children` | `React.ReactNode \| (values: CheckboxButtonRenderProps) => React.ReactNode` | - | 按钮内容(控件 + 标签)或按钮 render prop |
| `className` | `string \| (values: CheckboxButtonRenderProps) => string` | - | 应用于可点击 label 的类 |
### CheckboxFieldRenderProps
在根 `Checkbox` 上使用 render prop 时,提供以下字段级值:
| Prop | 类型 | 描述 |
| ----------------- | --------- | --------- |
| `isSelected` | `boolean` | 是否当前选中 |
| `isIndeterminate` | `boolean` | 是否处于不确定状态 |
| `isDisabled` | `boolean` | 是否禁用 |
| `isReadOnly` | `boolean` | 是否只读 |
| `isInvalid` | `boolean` | 是否无效 |
| `isRequired` | `boolean` | 是否必填 |
### CheckboxButtonRenderProps
`Checkbox.Control` 与 `Checkbox.Indicator` 使用按钮级 render props(`isHovered`、`isPressed`、`isFocusVisible` 等)。将函数作为 `Checkbox.Control` 子节点或传给 `Checkbox.Indicator` 以访问它们。
## 相关组件
## Related Components
* **Label**: Accessible label for form controls
* **CheckboxGroup**: Group of checkboxes with shared state
* **Description**: Helper text for form fields
# Description 描述
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/description
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/description.mdx
> 为表单字段及其他组件提供补充说明文本
## 用法
```tsx
import { Description } from '@heroui/react';
```
```tsx
import {Description, Input, Label} from "@heroui/react";
export function Basic() {
return (
邮箱
我们不会将你的邮箱分享给任何人。
);
}
```
## 示例
### 搭配表单字段
```tsx
Password
至少 8 个字符,且包含一个大写字母
```
### 与 TextField 配合
```tsx
import {TextField, Label, Input, Description} from '@heroui/react';
Email
We'll never share your email
```
使用 [TextField](./text-field) 组件时,无障碍属性会自动应用到标签与描述。
## 自定义样式
### Tailwind CSS
```tsx
import {Description, Input, Label} from "@heroui/react";
export function CustomStyles() {
return (
工作区 URL
仅支持小写字母和连字符。将用于 app.heroui.com/acme
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Description 组件类。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.description {
@apply text-muted;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Description 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/description.css)):
#### 基础类 \[!toc]
* `.description` - 基础描述样式,使用 `muted` 文本颜色
## API 参考
### Description
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | 描述内容 |
## 无障碍
Description 组件通过以下方式增强无障碍性:
* 使用屏幕阅读器可识别的语义化 HTML
* 提供 `slot="description"` 属性以集成 React Aria
* 支持适当的文本对比度
## 相关组件
## 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
# ErrorMessage 错误消息
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/error-message
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/error-message.mdx
> 用于展示错误的底层错误消息组件
## 用法
```tsx
import { ErrorMessage } from '@heroui/react';
```
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Description, ErrorMessage, Label, Tag, TagGroup} from "@heroui/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)}
>
必选分类
新闻
旅游
游戏
购物
请至少选择一个分类
{!!isInvalid && <>请至少选择一个分类>}
);
}
```
`ErrorMessage` 是基于 React Aria `Text` 组件、`errorMessage` slot 的底层组件,用于在 **非表单组件**(如 `TagGroup`、`Calendar` 及其他集合类组件)中展示错误消息。
## 组件结构
```tsx
import { TagGroup, Tag, Label, Description, ErrorMessage } from '@heroui/react';
```
## 何时使用
`ErrorMessage` **不绑定表单**,是用于非表单上下文的通用错误展示组件。
* **推荐用于** 非表单组件(如 `TagGroup`、`Calendar`、集合类组件)
* **表单字段** 建议使用 [`FieldError`](/docs/react/components/field-error),它提供表单专用校验特性与自动错误处理,遵循标准化表单校验模式。
## ErrorMessage 与 FieldError
| 组件 | 使用场景 | 表单集成 | 示例组件 |
| -------------- | -------- | ---- | ---------------------------------- |
| `ErrorMessage` | 非表单组件 | 否 | `TagGroup`、`Calendar` |
| `FieldError` | 表单字段(推荐) | 是 | `TextField`、`NumberField`、`Select` |
表单校验建议使用 `FieldError`,它遵循标准化表单校验模式并提供表单专用特性。参见 [FieldError 文档](/docs/react/components/field-error) 与 [Form 指南](/docs/react/components/form)。
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Description, ErrorMessage, Label, Tag, TagGroup} from "@heroui/react";
import {useMemo, useState} from "react";
export function CustomStyles() {
const [selected, setSelected] = useState>(new Set());
const isInvalid = useMemo(() => Array.from(selected).length === 0, [selected]);
return (
主题
API
设计
文档
请至少选择一个主题
{!!isInvalid && <>请至少选择一个主题>}
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ErrorMessage 组件类。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.error-message {
@apply text-sm font-medium text-danger;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ErrorMessage 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/error-message.css)):
#### 基础类 \[!toc]
* `.error-message` - 基础错误消息样式,danger 色与文本截断
#### 插槽类 \[!toc]
* `[slot="errorMessage"]` - 用于 React Aria 集成的 ErrorMessage slot 样式
## API 参考
### ErrorMessage
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | 错误消息内容 |
## 说明
`ErrorMessage` 基于 React Aria 的 `Text` 组件,`slot="errorMessage"`。可通过 `[slot=errorMessage]` CSS 选择器定位。
## 无障碍
ErrorMessage 组件通过以下方式增强无障碍性:
* 使用屏幕阅读器可识别的语义化 HTML
* 提供 `slot="errorMessage"` 属性以集成 React Aria
* 错误状态支持适当的文本对比度
* 遵循 WAI-ARIA 错误消息最佳实践
## 相关组件
## Related Components
* **TagGroup**: Focusable list of tags with selection and removal support
# FieldError 字段错误
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/field-error
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/field-error.mdx
> 展示表单字段校验错误消息
## 用法
```tsx
import { FieldError } from '@heroui/react';
```
```tsx
"use client";
import {FieldError, Input, Label, TextField} from "@heroui/react";
import {useState} from "react";
export function Basic() {
const [value, setValue] = useState("jr");
const isInvalid = value.length > 0 && value.length < 3;
return (
用户名
setValue(e.target.value)}
/>
用户名至少需要 3 个字符
);
}
```
FieldError 组件展示表单字段的校验错误消息。当父字段标记为无效时自动显示,并提供平滑的透明度过渡。
## 示例
### 基础校验
```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
);
}
```
### 动态错误信息
```tsx
0}>
Password
{(validation) => validation.validationErrors.join(', ')}
```
### 自定义校验逻辑
```tsx
function EmailField() {
const [email, setEmail] = useState('');
const isInvalid = email.length > 0 && !email.includes('@');
return (
Email
setEmail(e.target.value)}
/>
Email must include @ symbol
);
}
```
### 多条错误信息
```tsx
Username
{errors.map((error, i) => (
{error}
))}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {FieldError, Input, Label, TextField} from "@heroui/react";
import {useState} from "react";
export function CustomStyles() {
const [value, setValue] = useState("jr");
const isInvalid = value.length > 0 && value.length < 3;
return (
用户名
setValue(e.target.value)}
/>
用户名至少需要 3 个字符
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 FieldError 组件类。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.field-error {
@apply font-medium text-danger;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
FieldError 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/field-error.css)):
#### 基础类 \[!toc]
* `.field-error` - 基础错误样式,danger 色
* 仅在存在 `data-visible` 属性时显示
* 长消息以省略号截断
## API 参考
### FieldError
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------ | --- | ----------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| ((validation: ValidationResult) => ReactNode)` | - | 错误消息内容或 render 函数 |
## 无障碍
FieldError 组件通过以下方式确保无障碍性:
* 使用适当的 ARIA 属性播报错误
* 语义化 HTML 支持屏幕阅读器
* 提供视觉与程序化错误指示
* 根据校验状态自动管理可见性
## 相关组件
## 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
# Fieldset 字段组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/fieldset
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/fieldset.mdx
> 将相关表单控件与图例、描述和操作组合在一起
## 用法
```tsx
import { Fieldset } from '@heroui/react';
```
```tsx
"use client";
import {FloppyDisk} from "@gravity-ui/icons";
import {
Button,
Description,
FieldError,
FieldGroup,
Fieldset,
Form,
Input,
Label,
TextArea,
TextField,
} from "@heroui/react";
export function Basic() {
const onSubmit = (e: React.SyntheticEvent) => {
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("表单提交成功!");
};
return (
个人资料设置
更新你的个人资料信息。
{
if (value.length < 3) {
return "姓名至少需要 3 个字符";
}
return null;
}}
>
姓名
邮箱
{
if (value.length < 10) {
return "简介至少需要 10 个字符";
}
return null;
}}
>
简介
至少 10 个字符
保存更改
取消
);
}
```
## 组件结构
```tsx
import { Fieldset } from '@heroui/react';
export default () => (
{/* form fields go here */}
{/* action buttons go here */}
)
```
## 示例
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请在表单控件(Input、TextArea 等)上使用 `variant="secondary"`,以应用适合 Surface 背景的低强调变体。
```tsx
"use client";
import {FloppyDisk} from "@gravity-ui/icons";
import {
Button,
Description,
FieldError,
Fieldset,
Form,
Input,
Label,
Surface,
TextArea,
TextField,
} from "@heroui/react";
import React from "react";
export function OnSurface() {
const onSubmit = (e: React.SyntheticEvent) => {
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("表单提交成功!");
};
return (
个人资料设置
更新你的个人资料信息。
{
if (value.length < 3) {
return "姓名至少需要 3 个字符";
}
return null;
}}
>
姓名
邮箱
{
if (value.length < 10) {
return "简介至少需要 10 个字符";
}
return null;
}}
>
简介
至少 10 个字符
保存更改
取消
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {FloppyDisk} from "@gravity-ui/icons";
import {
Button,
Description,
FieldError,
FieldGroup,
Fieldset,
Form,
Input,
Label,
TextArea,
TextField,
} from "@heroui/react";
const shell =
"rounded-xl border border-border/70 bg-linear-to-b from-neutral-50/90 to-white p-4 ring-1 ring-black/5 dark:from-neutral-900/80 dark:to-neutral-900 dark:ring-white/10";
const field =
"rounded-xl border border-border/80 bg-surface shadow-sm ring-1 ring-black/5 transition-[box-shadow,border-color] focus-visible:ring-2 focus-visible:ring-neutral-400/25 dark:ring-white/10 dark:focus-visible:ring-neutral-500/30";
export function CustomStyles() {
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
alert("表单提交成功!");
};
return (
个人资料设置
更新你的个人资料信息。
{
if (value.length < 3) {
return "姓名至少需要 3 个字符";
}
return null;
}}
>
姓名
邮箱
{
if (value.length < 10) {
return "简介至少需要 10 个字符";
}
return null;
}}
>
简介
至少 10 个字符
保存更改
取消
);
}
```
### 全局 CSS
使用 `@layer components` 指令定位 Fieldset 的 [BEM](https://getbem.com/) 风格类。
```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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Fieldset 复合组件暴露以下 CSS 选择器:
#### 基础类 \[!toc]
* `.fieldset` – 根容器
* `.fieldset__legend` – 图例元素
* `.fieldset__field-group` – 分组字段包裹层
* `.fieldset__actions` – 字段下方的操作栏
## API 参考
### Fieldset
| Prop | 类型 | 默认值 | 描述 |
| ------------- | ------------------------------------------- | ------------------- | ------------------------ |
| `className` | `string` | - | 应用于根元素的 Tailwind CSS 类 |
| `children` | `React.ReactNode` | - | Fieldset 内容(图例、分组、描述、操作) |
| `nativeProps` | `React.HTMLAttributes` | 支持原生 fieldset 属性与事件 | |
### Fieldset.Legend
| Prop | 类型 | 默认值 | 描述 |
| ------------- | ----------------------------------------- | --- | ---------------- |
| `className` | `string` | - | 图例元素的 Tailwind 类 |
| `children` | `React.ReactNode` | - | 图例内容,通常为纯文本 |
| `nativeProps` | `React.HTMLAttributes` | - | 原生 legend 属性 |
### Fieldset.Group
| Prop | 类型 | 默认值 | 描述 |
| ------------- | -------------------------------------- | --- | ------------------ |
| `className` | `string` | - | 分组字段的布局与间距类 |
| `children` | `React.ReactNode` | - | fieldset 内要分组的表单控件 |
| `nativeProps` | `React.HTMLAttributes` | - | 原生 div 属性 |
### Fieldset.Actions
| Prop | 类型 | 默认值 | 描述 |
| ------------- | -------------------------------------- | --- | --------------------- |
| `className` | `string` | - | 对齐操作按钮或文本的 Tailwind 类 |
| `children` | `React.ReactNode` | - | 操作按钮或辅助文本 |
| `nativeProps` | `React.HTMLAttributes` | - | 原生 div 属性 |
## 相关案例
## 相关组件
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **Label**: Accessible label for form controls
* **CheckboxGroup**: Group of checkboxes with shared state
# Form 表单
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/form
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/form.mdx
> 用于表单校验与提交处理的包裹组件
## 用法
```tsx
import { Form } from '@heroui/react';
```
```tsx
"use client";
import {Check} from "@gravity-ui/icons";
import {Button, Description, FieldError, Form, Input, Label, TextField} from "@heroui/react";
export function Basic() {
const onSubmit = (e: React.SyntheticEvent) => {
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(`表单提交数据:${JSON.stringify(data, null, 2)}`);
};
return (
{
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return "请输入有效的邮箱地址";
}
return null;
}}
>
邮箱
{
if (value.length < 8) {
return "密码至少需要 8 个字符";
}
if (!/[A-Z]/.test(value)) {
return "密码至少需要包含一个大写字母";
}
if (!/[0-9]/.test(value)) {
return "密码至少需要包含一个数字";
}
return null;
}}
>
密码
至少 8 个字符,且包含 1 个大写字母和 1 个数字
提交
重置
);
}
```
## 组件结构
```tsx
import {Form, Button} from '@heroui/react';
export default () => (
{/* Form fields go here */}
)
```
## 示例
### 渲染函数
```tsx
"use client";
import {Check} from "@gravity-ui/icons";
import {Button, Description, FieldError, Form, Input, Label, TextField} from "@heroui/react";
export function RenderFunction() {
const onSubmit = (e: React.SyntheticEvent) => {
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(`表单提交数据:${JSON.stringify(data, null, 2)}`);
};
return (
}
onSubmit={onSubmit}
>
{
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return "请输入有效的邮箱地址";
}
return null;
}}
>
邮箱
{
if (value.length < 8) {
return "密码至少需要 8 个字符";
}
if (!/[A-Z]/.test(value)) {
return "密码至少需要包含一个大写字母";
}
if (!/[0-9]/.test(value)) {
return "密码至少需要包含一个数字";
}
return null;
}}
>
密码
至少 8 个字符,且包含 1 个大写字母和 1 个数字
提交
重置
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Button, Form, Input, Label, TextField} from "@heroui/react";
export function CustomStyles() {
return (
e.preventDefault()}
>
工作邮箱
继续
);
}
```
### 全局 CSS
要自定义表单布局与间距,可在 `` 上使用 `className` prop,或通过 `@layer components` 添加项目级类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
`Form` 渲染原生 ` ` 元素,聚焦校验与提交。`@heroui/styles` 中无专用 BEM 类——从全局 CSS 应用容器样式,控件级自定义请使用字段组件。
```css
@layer components {
.form-layout {
@apply flex flex-col gap-4 rounded-xl border border-border bg-surface p-4 shadow-sm;
}
}
```
```tsx
{/* TextField, Input, Button, etc. */}
```
分组字段与共享布局请使用 [Fieldset](./fieldset),并定位 `.fieldset`、`.fieldset__legend` 等相关类。单个控件请参阅 [TextField](./text-field)、[Input](./input)、[Label](./label)、[FieldError](./field-error) 的 **Global CSS** 部分。
## 样式参考
HeroUI 对在 `@heroui/styles` 中提供样式的组件遵循 [BEM](https://getbem.com/) 方法论。
`Form` 渲染原生 `` 元素,无专用 BEM 类。通过 `className` 应用布局、间距与表面样式。字段外观与校验状态来自 `TextField`、`Input`、`Label`、`Description`、`FieldError` 等子组件。结构化多字段布局请与 [Fieldset](./fieldset) 组合使用。
## API 参考
### Form
Form 组件是 React Aria Form 原语的包裹层,提供表单校验与提交处理能力。
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ------------------------------------------------------------------------------ | ---------- | ---------------------------------------------------- |
| `action` | `string \| FormHTMLAttributes['action']` | - | 提交表单数据的 URL |
| `className` | `string` | - | 应用于 form 元素的 Tailwind CSS 类 |
| `children` | `React.ReactNode` | - | 表单内容(字段、按钮等) |
| `encType` | `'application/x-www-form-urlencoded' \| 'multipart/form-data' \| 'text/plain'` | - | 表单数据提交的编码类型 |
| `method` | `'get' \| 'post'` | - | 提交表单时使用的 HTTP 方法 |
| `onInvalid` | `(event: SyntheticEvent) => void` | - | 表单校验失败时调用。默认聚焦第一个无效字段。使用 `preventDefault()` 可自定义聚焦行为 |
| `onReset` | `(event: SyntheticEvent) => void` | - | 表单重置时调用 |
| `onSubmit` | `(event: SyntheticEvent) => void` | - | 表单提交时调用 |
| `target` | `'_self' \| '_blank' \| '_parent' \| '_top'` | - | 提交后显示响应的位置 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 校验还是 ARIA 校验。`native` 阻止提交,`aria` 实时显示错误 |
| `validationErrors` | `ValidationErrors` | - | 按字段名映射的服务端校验错误。立即显示,用户修改字段时清除 |
| `aria-label` | `string` | - | 表单的无障碍标签 |
| `aria-labelledby` | `string` | - | 标注表单的元素 ID。提供时创建 form landmark |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Form Validation
Form 组件集成 React Aria 校验系统,支持:
* 使用内置 HTML5 校验属性(`required`、`minLength`、`pattern` 等)
* 在 TextField 组件上提供自定义校验函数
* 使用 FieldError 组件展示校验错误
* 在正确校验后处理表单提交
* 通过 `validationErrors` prop 提供服务端校验错误
#### Validation Behavior
`validationBehavior` prop 控制校验展示方式:
* **`native`**(默认):使用原生 HTML 校验,有错误时阻止提交
* **`aria`**:使用 ARIA 属性校验,用户输入时实时显示错误,不阻止提交
可在表单级或单个字段级设置此行为。
### Form Submission
表单可通过多种方式提交:
* **传统提交**:设置 `action` prop 提交到 URL
* **JavaScript 处理**:使用 `onSubmit` 处理表单数据
* **FormData API**:在 submit 处理函数中使用 FormData API 访问表单数据
FormData 示例:
```tsx
import type {SyntheticEvent} from "react";
function handleSubmit(e: SyntheticEvent) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData);
console.log('Form data:', data);
}
```
### Integration with Form Fields
Form 组件与 HeroUI 表单字段组件无缝协作:
* **TextField**:带标签与校验的文本输入
* **Checkbox**:布尔选择
* **RadioGroup**:多选一
* **Switch**:开关控件
* **Button**:提交与重置操作
所有字段组件放在 Form 内时会自动集成 Form 的校验与提交行为。
### Advanced Usage
更高级用法包括:
* 自定义校验上下文
* 表单 context provider
* 与第三方库集成
* 校验错误时的自定义焦点管理
请参阅 [React Aria Form 文档](https://react-spectrum.adobe.com/react-aria/Form.html)。
## 无障碍
使用 React Aria 组件时,表单默认可访问。主要特性包括:
* 原生 `` 元素语义
* 使用 `aria-label` 或 `aria-labelledby` 创建 form landmark
* 校验错误时自动焦点管理
* 使用 `validationBehavior="aria"` 时的 ARIA 校验属性
## 相关案例
## 相关组件
## 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
# InputGroup 输入框组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/input-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/input-group.mdx
> 将相关输入控件与前后缀元素组合,以增强表单字段
## 用法
```tsx
import { InputGroup } from '@heroui/react';
```
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function Default() {
return (
邮箱地址
);
}
```
## 组件结构
```tsx
import {InputGroup, TextField, Label} from '@heroui/react';
export default () => (
{/* Or use InputGroup.TextArea for multiline input */}
)
```
> **InputGroup** 使用可选的前缀与后缀包裹输入框,形成视觉上统一的组合。通常放在 **[TextField](/docs/react/components/text-field)** 内,用于在输入前后添加图标、文字、按钮等元素。单行输入请使用 **InputGroup.Input**,多行输入请使用 **InputGroup.TextArea**。
## 示例
### 变体
InputGroup 组件支持两种视觉变体:
* **`primary`**(默认)- 标准样式带阴影,适用于大多数场景
* **`secondary`** - 低强调变体无阴影,适用于 Surface 组件内
```tsx
import {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function Variants() {
return (
主要变体
次要变体
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, Surface, TextField} from "@heroui/react";
export function OnSurface() {
return (
邮箱地址
我们不会将此邮箱分享给任何人
);
}
```
### 后缀加载状态
在后缀显示加载 spinner 以表示正在处理。
```tsx
"use client";
import {InputGroup, Spinner, TextField} from "@heroui/react";
export function WithLoadingSuffix() {
return (
);
}
```
### 必填字段
InputGroup 会遵循父级 TextField 的必填状态。
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, TextField} from "@heroui/react";
export function Required() {
return (
邮箱地址
设置价格
$
USD
客户将支付的价格
);
}
```
### 禁用状态
InputGroup 会遵循父级 TextField 的禁用状态。
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function Disabled() {
return (
邮箱地址
设置价格
$
USD
);
}
```
### 宽度充满
```tsx
import {Envelope, Eye} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function FullWidth() {
return (
邮箱地址
密码
);
}
```
### 文字前缀
使用文字作为前缀,例如货币符号或协议前缀。
```tsx
"use client";
import {InputGroup, Label, TextField} from "@heroui/react";
export function WithTextPrefix() {
return (
网站
https://
);
}
```
### 文字后缀
使用文字作为后缀,例如域名后缀或单位。
```tsx
"use client";
import {InputGroup, Label, TextField} from "@heroui/react";
export function WithTextSuffix() {
return (
网站
.com
);
}
```
### 图标前缀与文字后缀
组合图标前缀与文字后缀。
```tsx
"use client";
import {Globe} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function WithIconPrefixAndTextSuffix() {
return (
网站
.com
);
}
```
### 复制按钮后缀
在后缀中加入交互按钮,例如复制按钮。
```tsx
"use client";
import {Copy} from "@gravity-ui/icons";
import {Button, InputGroup, Label, TextField} from "@heroui/react";
export function WithCopySuffix() {
return (
网站
);
}
```
### 图标前缀与复制按钮
组合图标前缀与交互式后缀按钮。
```tsx
"use client";
import {Copy, Globe} from "@gravity-ui/icons";
import {Button, InputGroup, Label, TextField} from "@heroui/react";
export function WithIconPrefixAndCopySuffix() {
return (
网站
);
}
```
### 密码可见性切换
在后缀中使用按钮切换密码可见性。
```tsx
"use client";
import {Eye, EyeSlash} from "@gravity-ui/icons";
import {Button, InputGroup, Label, TextField} from "@heroui/react";
import {useState} from "react";
export function PasswordWithToggle() {
const [isVisible, setIsVisible] = useState(false);
return (
密码
setIsVisible(!isVisible)}
>
{isVisible ? : }
);
}
```
### 键盘快捷键
使用 [Kbd](/docs/react/components/kbd) 组件展示键盘快捷键。
```tsx
"use client";
import {InputGroup, Kbd, TextField} from "@heroui/react";
export function WithKeyboardShortcut() {
return (
K
);
}
```
### 徽章后缀
在后缀中加入徽章或 chip,用于展示状态或标签。
```tsx
"use client";
import {Chip, InputGroup, TextField} from "@heroui/react";
export function WithBadgeSuffix() {
return (
Pro
);
}
```
### 表单校验
InputGroup 会自动反映父级 TextField 的无效状态。
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {FieldError, InputGroup, Label, TextField} from "@heroui/react";
export function Invalid() {
return (
邮箱地址
请输入有效的邮箱地址
设置价格
$
USD
价格必须大于 0
);
}
```
### 前缀图标
在输入框前添加图标。
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, TextField} from "@heroui/react";
export function WithPrefixIcon() {
return (
邮箱地址
我们不会将此邮箱分享给任何人
);
}
```
### 后缀图标
在输入框后添加图标。
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Description, InputGroup, Label, TextField} from "@heroui/react";
export function WithSuffixIcon() {
return (
邮箱地址
我们不会发送垃圾邮件
);
}
```
### 前缀与后缀
同时组合前缀与后缀。
```tsx
"use client";
import {Description, InputGroup, Label, TextField} from "@heroui/react";
export function WithPrefixAndSuffix() {
return (
设置价格
$
USD
客户将支付的价格
);
}
```
### 文本域
多行输入请使用 **InputGroup.TextArea**,并搭配前缀与后缀。当存在 textarea 时,容器高度会自动适应内容,并将前缀/后缀与顶部对齐。
```tsx
"use client";
import {ArrowUp, At, Microphone, PlugConnection, Plus} from "@gravity-ui/icons";
import {Button, InputGroup, Kbd, Spinner, TextField, Tooltip} from "@heroui/react";
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 (
添加上下文
setValue(event.target.value)}
/>
添加文件等
连接应用
语音输入
{({isPending}) => (isPending ? : )}
发送
);
}
```
### 用法示例
```tsx
import {InputGroup, TextField, Label, Button} from '@heroui/react';
import {Icon} from '@iconify/react';
function Example() {
return (
Email
);
}
```
### 文本域用法示例
```tsx
import {Envelope} from "@gravity-ui/icons";
import {Description, FieldError, InputGroup, Label, TextField} from "@heroui/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
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {InputGroup, Label, TextField} from "@heroui/react";
export function CustomStyles() {
return (
工作邮箱
);
}
```
### 全局 CSS
InputGroup 使用可自定义的 CSS 类。覆盖组件类以匹配设计系统。
```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);
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
#### 基础类 \[!toc]
* `.input-group` – 根容器:带边框、背景与 flex 布局。默认 `min-h-9` 与 `items-center`;存在 textarea 时切换为 `items-start`
* `.input-group__input` – 透明背景、无边框的输入元素。textarea 也使用该基础类
* `.input-group__prefix` – 左侧圆角的前缀容器。与 textarea 搭配时与顶部对齐
* `.input-group__suffix` – 右侧圆角的后缀容器。与 textarea 搭配时与顶部对齐
#### 变体类 \[!toc]
* `.input-group--primary` – 带阴影的主变体(默认)
* `.input-group--secondary` – 无阴影的次变体,适用于 Surface 内
**Note:** 使用 `InputGroup.TextArea` 时,容器从 `items-center` 切换为 `items-start`,并使用 `height: auto` 替代固定高度。前缀与后缀与顶部对齐,并增加内边距以匹配 textarea 的垂直内边距。textarea 使用相同的 `.input-group__input` 基础类,并通过 `[data-slot="input-group-textarea"]` 选择器应用 textarea 专用样式(最小高度与纵向 resize)。
### 交互状态
InputGroup 会根据状态自动管理以下 data 属性:
* **Hover**:`[data-hovered]` - 悬停在组合上时应用
* **Focus Within**:`[data-focus-within]` - 输入聚焦时应用
* **Invalid**:`[data-invalid]` - 父级 TextField 无效时应用
* **Disabled**:`[data-disabled]` 或 `[aria-disabled]` - 父级 TextField 禁用时应用
## API 参考
### InputGroup
InputGroup 继承 React Aria [Group](https://react-spectrum.adobe.com/react-aria/Group.html) 组件的全部 props。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------------------------------------------------------- | ------- | -------------------------------------------- |
| `children` | `React.ReactNode \| (values: GroupRenderProps) => React.ReactNode` | - | 子组件(Input、TextArea、Prefix、Suffix)或 render 函数 |
| `className` | `string \| (values: GroupRenderProps) => string` | - | CSS 类,支持 render props |
| `style` | `React.CSSProperties \| (values: GroupRenderProps) => React.CSSProperties` | - | 行内样式,支持 render props |
| `fullWidth` | `boolean` | `false` | 输入组是否占满容器宽度 |
| `id` | `string` | - | 元素唯一标识符 |
#### Variant Props
| Prop | 类型 | 默认值 | 描述 |
| --------- | -------------------------- | ----------- | --------------------------------------------------------- |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调无阴影,适用于 Surface 内 |
#### Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | --------------------------------------- | --------- | --------------------------------------------- |
| `aria-label` | `string` | - | 无可见标签时的无障碍标签 |
| `aria-labelledby` | `string` | - | 标注该组的元素 ID |
| `aria-describedby` | `string` | - | 描述该组的元素 ID |
| `aria-details` | `string` | - | 包含更多详情的元素 ID |
| `role` | `'group' \| 'region' \| 'presentation'` | `'group'` | 分组的无障碍角色。重要内容用 `region`,纯视觉分组用 `presentation` |
### Composition Components
InputGroup 与以下子组件配合使用:
* **InputGroup.Root** - 根容器(也可写作 `InputGroup`)
* **InputGroup.Input** - 单行输入元素组件
* **InputGroup.TextArea** - 多行 textarea 元素组件
* **InputGroup.Prefix** - 前缀容器组件
* **InputGroup.Suffix** - 后缀容器组件
#### InputGroup.Input
InputGroup.Input 继承 React Aria [Input](https://react-spectrum.adobe.com/react-aria/Input.html) 组件的全部 props。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | -------------------------- | ----------- | --------------------------- |
| `className` | `string` | - | CSS 类 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 输入视觉变体 |
| `type` | `string` | `'text'` | 输入类型(text、password、email 等) |
| `value` | `string` | - | 当前值(受控) |
| `defaultValue` | `string` | - | 默认值(非受控) |
| `placeholder` | `string` | - | 占位文本 |
| `disabled` | `boolean` | - | 是否禁用 |
| `readOnly` | `boolean` | - | 是否只读 |
#### InputGroup.TextArea
InputGroup.TextArea 继承 React Aria [TextArea](https://react-spectrum.adobe.com/react-aria/TextArea.html) 组件的全部 props。
| Prop | 类型 | 默认值 | 描述 |
| -------------- | -------------------------- | ----------- | ------------- |
| `className` | `string` | - | CSS 类 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | textarea 视觉变体 |
| `value` | `string` | - | 当前值(受控) |
| `defaultValue` | `string` | - | 默认值(非受控) |
| `placeholder` | `string` | - | 占位文本 |
| `rows` | `number` | - | 可见文本行数 |
| `disabled` | `boolean` | - | 是否禁用 |
| `readOnly` | `boolean` | - | 是否只读 |
#### InputGroup.Prefix
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ------------ |
| `children` | `React.ReactNode` | - | 前缀内容(图标、文字等) |
| `className` | `string` | - | CSS 类 |
#### InputGroup.Suffix
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | --------------- |
| `children` | `React.ReactNode` | - | 后缀内容(图标、按钮、徽章等) |
| `className` | `string` | - | CSS 类 |
## 相关案例
## 相关组件
## 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
# InputOTP 一次性密码输入
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/input-otp
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/input-otp.mdx
> 用于验证码与安全认证的一次性密码输入组件
## 用法
```tsx
import { InputOTP } from '@heroui/react';
```
```tsx
import {InputOTP, Label, Link} from "@heroui/react";
export function Basic() {
return (
验证账户
我们已向 a****@gmail.com 发送验证码
);
}
```
## 组件结构
```tsx
import { InputOTP } from '@heroui/react';
export default () => (
{/* ...rest of the slots */}
{/* ...rest of the slots */}
)
```
> **InputOTP** 基于 [@guilherme\_rodz](https://twitter.com/guilherme_rodz) 的 [input-otp](https://github.com/guilhermerodz/input-otp) 构建,为 OTP 输入组件提供灵活且无障碍的基础。
## 示例
### 变体
InputOTP 组件支持两种视觉变体:
* **`primary`**(默认)- 标准样式带阴影,适用于大多数场景
* **`secondary`** - 低强调变体无阴影,适用于 Surface 组件内
```tsx
import {InputOTP, Label} from "@heroui/react";
export function Variants() {
return (
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
import {InputOTP, Label, Link, Surface} from "@heroui/react";
export function OnSurface() {
return (
验证账户
我们已向 a****@gmail.com 发送验证码
);
}
```
### 禁用状态
```tsx
import {Description, InputOTP, Label} from "@heroui/react";
export function Disabled() {
return (
验证账户
验证码校验当前已禁用
);
}
```
### 四位验证码
```tsx
import {InputOTP, Label} from "@heroui/react";
export function FourDigits() {
return (
输入 PIN
);
}
```
### 受控组件
控制 value 以与状态同步、清空输入或实现自定义校验。
```tsx
"use client";
import {Description, InputOTP, Label} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("");
return (
验证账户
{value.length > 0 ? (
<>
值:{value} ({value.length}/6) •{" "}
setValue("")}>
Clear
>
) : (
"请输入 6 位验证码"
)}
);
}
```
### 输入完成回调
使用 `onComplete` 回调在所有 slot 填满时触发操作。
```tsx
"use client";
import {Button, Form, InputOTP, Label, Spinner} from "@heroui/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.SyntheticEvent) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
setIsSubmitting(false);
setValue("");
setIsComplete(false);
}, 2000);
};
return (
验证账户
{
setValue(val);
setIsComplete(false);
}}
>
{isSubmitting ? (
<>
验证中…
>
) : (
"验证验证码"
)}
);
}
```
### 表单示例
完整的双因素认证表单,含校验与提交。
```tsx
"use client";
import {Button, Description, Form, InputOTP, Label, Link, Spinner} from "@heroui/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.SyntheticEvent) => {
e.preventDefault();
setError("");
if (value.length !== 6) {
setError("请输入全部 6 位数字");
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
if (value === "123456") {
console.log("Code verified successfully!");
setValue("");
} else {
setError("验证码无效,请重试。");
}
setIsSubmitting(false);
}, 1500);
};
return (
双重身份验证
请输入身份验证器应用中的 6 位验证码
{
setValue(val);
setError("");
}}
>
{error}
{isSubmitting ? (
<>
验证中…
>
) : (
"验证"
)}
);
}
```
### 限定格式
使用 `pattern` prop 限制输入字符。HeroUI 导出 `REGEXP_ONLY_CHARS`、`REGEXP_ONLY_DIGITS` 等常用模式。
```tsx
import {Description, InputOTP, Label, REGEXP_ONLY_CHARS} from "@heroui/react";
export function WithPattern() {
return (
输入验证码(仅字母)
仅允许输入字母
);
}
```
### 带校验
配合 `isInvalid` 与校验消息展示错误。
```tsx
"use client";
import {Button, Description, Form, InputOTP, Label} from "@heroui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState("");
const [isInvalid, setIsInvalid] = React.useState(false);
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const code = formData.get("code");
if (code !== "123456") {
setIsInvalid(true);
return;
}
setIsInvalid(false);
setValue("");
alert("验证码校验成功!");
};
const handleChange = (val: string) => {
setValue(val);
setIsInvalid(false);
};
return (
验证账户
提示:验证码为 123456
验证码无效,请重试。
提交
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {InputOTP, Label, Link} from "@heroui/react";
const slotClass =
"rounded-lg border-border/80 bg-default data-[active=true]:border-accent/40 data-[active=true]:bg-accent-soft";
export function CustomStyles() {
return (
验证账户
重新发送
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 InputOTP 组件类。
[了解更多](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-accent-500 ring-2 ring-accent-200;
}
.input-otp__separator {
@apply w-2 h-1 bg-border-strong rounded-full;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
InputOTP 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/input-otp.css)):
#### 基础类 \[!toc]
* `.input-otp` - 基础容器
* `.input-otp__container` - input-otp 库的内部容器
* `.input-otp__group` - slot 组
* `.input-otp__slot` - 单个输入 slot
* `.input-otp__slot-value` - slot 内的字符
* `.input-otp__caret` - 闪烁光标指示器
* `.input-otp__separator` - 组之间的视觉分隔符
#### 状态类 \[!toc]
* `.input-otp__slot[data-active="true"]` - 当前激活的 slot
* `.input-otp__slot[data-filled="true"]` - 含字符的 slot
* `.input-otp__slot[data-disabled="true"]` - 禁用的 slot
* `.input-otp__slot[data-invalid="true"]` - 无效的 slot
* `.input-otp__container[data-disabled="true"]` - 禁用的容器
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Hover**:slot 上 `:hover` 或 `[data-hovered="true"]`
* **Active**:slot 上 `[data-active="true"]`(当前聚焦)
* **Filled**:slot 上 `[data-filled="true"]`(含字符)
* **Disabled**:容器与 slot 上 `[data-disabled="true"]`
* **Invalid**:slot 上 `[data-invalid="true"]`
## API 参考
### InputOTP
InputOTP 基于 [input-otp](https://github.com/guilhermerodz/input-otp) 库构建,并附加额外特性。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | -------------------------- | ----------- | --------------------------------------------------------- |
| `maxLength` | `number` | - | **必填。** 输入 slot 数量 |
| `value` | `string` | - | 受控值(未提供则为非受控) |
| `onChange` | `(value: string) => void` | - | 值变化时的回调 |
| `onComplete` | `(value: string) => void` | - | 所有 slot 填满时的回调 |
| `className` | `string` | - | 容器的附加 CSS 类 |
| `containerClassName` | `string` | - | 内部容器的 CSS 类 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调无阴影,适用于 Surface 内 |
| `children` | `React.ReactNode` | - | InputOTP.Group、InputOTP.Slot 与 InputOTP.Separator 组件 |
#### Validation Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------- | --------------- | ------- | ----------- |
| `isDisabled` | `boolean` | `false` | 是否禁用 |
| `isInvalid` | `boolean` | `false` | 是否处于无效状态 |
| `validationErrors` | `string[]` | - | 服务端或自定义校验错误 |
| `validationDetails` | `ValidityState` | - | HTML5 校验详情 |
#### Input Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | --------------------------------------------------------------------------- | ----------- | --------------------------------- |
| `pattern` | `string` | - | 允许字符的正则模式(如 `REGEXP_ONLY_DIGITS`) |
| `textAlign` | `'left' \| 'center' \| 'right'` | `'left'` | slot 内文本对齐 |
| `inputMode` | `'numeric' \| 'text' \| 'decimal' \| 'tel' \| 'search' \| 'email' \| 'url'` | `'numeric'` | 移动设备虚拟键盘类型 |
| `placeholder` | `string` | - | 空 slot 的占位文本 |
| `pasteTransformer` | `(text: string) => string` | - | 转换粘贴文本(如移除连字符) |
#### Form Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------- | --- | --------------- |
| `name` | `string` | - | 表单提交的 name 属性 |
| `autoFocus` | `boolean` | - | 挂载时是否聚焦第一个 slot |
### InputOTP.Group
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ---------------- |
| `className` | `string` | - | 组的附加 CSS 类 |
| `children` | `React.ReactNode` | - | InputOTP.Slot 组件 |
### InputOTP.Slot
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | -------------------- |
| `index` | `number` | - | **必填。** slot 的从零开始索引 |
| `className` | `string` | - | slot 的附加 CSS 类 |
### InputOTP.Separator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | ------------ |
| `className` | `string` | - | 分隔符的附加 CSS 类 |
### Exported Patterns
HeroUI 重新导出 input-otp 的常用正则模式以便使用:
```tsx
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from '@heroui/react';
// Use with pattern prop
{/* ... */}
```
* **REGEXP\_ONLY\_DIGITS** - 仅数字字符(0-9)
* **REGEXP\_ONLY\_CHARS** - 仅字母字符(a-z、A-Z)
* **REGEXP\_ONLY\_DIGITS\_AND\_CHARS** - 字母数字字符(0-9、a-z、A-Z)
## 相关组件
## Related Components
* **Input**: Single-line text input built on React Aria
* **Form**: Form validation and submission handling
* **Surface**: Base container surface
# Input 输入框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/input
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/input.mdx
> 接受标准 HTML 属性的原生单行文本输入组件
## 用法
```tsx
import { Input } from '@heroui/react';
```
```tsx
import {Input} from "@heroui/react";
export function Basic() {
return ;
}
```
校验、标签与错误消息请参阅 **[TextField](/docs/react/components/text-field)**。
## 示例
### 变体
Input 组件支持两种视觉变体:
* **`primary`**(默认)- 标准样式带阴影,适用于大多数场景
* **`secondary`** - 低强调变体无阴影,适用于 Surface 组件内
```tsx
import {Input} from "@heroui/react";
export function Variants() {
return (
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
import {Input, Surface} from "@heroui/react";
export function OnSurface() {
return (
);
}
```
### 宽度充满
```tsx
import {Input} from "@heroui/react";
export function FullWidth() {
return (
);
}
```
### 输入类型
```tsx
import {Input, Label} from "@heroui/react";
export function Types() {
return (
);
}
```
### 受控组件
```tsx
"use client";
import {Input} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("heroui.com");
return (
setValue(event.target.value)}
/>
https://{value || "你的域名"}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Input} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
基础类 `.input` 驱动每个实例。使用 `@layer components` 一次性覆盖。
```css
@layer components {
.input {
@apply rounded-lg border border-border bg-surface 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-accent ring-2 ring-accent/20;
}
&[data-invalid="true"] {
@apply border-danger bg-danger-soft text-danger;
}
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
#### 基础类 \[!toc]
* `.input` – 原生 input 元素样式
### 交互状态
* **Hover**:`:hover` 或 `[data-hovered="true"]`
* **Focus Visible**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **Invalid**:`[data-invalid="true"]`(与 `aria-invalid` 同步)
* **Disabled**:`:disabled` 或 `[aria-disabled="true"]`
* **Read Only**:`[aria-readonly="true"]`
## API 参考
### Input
Input 接受所有标准 HTML ` ` 属性,以及以下 props:
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ------------------------------------------------------ | ----------- | --------------------------------------------------------- |
| `className` | `string` | - | 与组件样式合并的 Tailwind 类 |
| `type` | `string` | `"text"` | 输入类型(text、email、password、number 等) |
| `value` | `string` | - | 受控值 |
| `defaultValue` | `string` | - | 非受控初始值 |
| `onChange` | `(event: React.ChangeEvent) => void` | - | 变化处理函数 |
| `placeholder` | `string` | - | 占位文本 |
| `disabled` | `boolean` | `false` | 是否禁用 |
| `readOnly` | `boolean` | `false` | 是否只读 |
| `required` | `boolean` | `false` | 是否必填 |
| `name` | `string` | - | 表单提交时的 name |
| `autoComplete` | `string` | - | 浏览器自动完成提示 |
| `maxLength` | `number` | - | 最大字符数 |
| `minLength` | `number` | - | 最小字符数 |
| `pattern` | `string` | - | 校验用正则模式 |
| `min` | `number \| string` | - | 最小值(number/date 输入) |
| `max` | `number \| string` | - | 最大值(number/date 输入) |
| `step` | `number \| string` | - | 步进间隔(number 输入) |
| `fullWidth` | `boolean` | `false` | 是否占满容器宽度 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调无阴影,适用于 Surface 内 |
> 校验 props 如 `isInvalid`、`isRequired` 与错误处理,请使用 **[TextField](/docs/react/components/text-field)** 并将 Input 作为子组件。
## 相关组件
## Related Components
* **TextField**: Composition-friendly fields with labels and validation
* **TextArea**: Multiline text input with focus management
* **Label**: Accessible label for form controls
# Label 标签
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/label
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/label.mdx
> 渲染与表单控件关联的无障碍标签。
## 用法
```tsx
import { Label } from '@heroui/react';
```
```tsx
import {Input, Label} from "@heroui/react";
export function Basic() {
return (
姓名
);
}
```
## 示例
### 带必填指示符
```tsx
Email Address
```
### 禁用状态
```tsx
Username
```
### 无效状态
```tsx
Password
```
## 自定义样式
### Tailwind CSS
```tsx
import {Input, Label} from "@heroui/react";
export function CustomStyles() {
return (
仓库
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.label {
@apply font-semibold text-foreground;
}
.label--required::after {
@apply text-accent;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Label 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/label.css)):
#### 基础类 \[!toc]
* `.label` — 基础标签文本样式
#### 状态修饰类 \[!toc]
* `.label--required` 或 `[data-required="true"] > .label` — 显示必填星号
* `.label--disabled` 或 `[data-disabled="true"] .label` — 禁用状态样式
* `.label--invalid` 或 `[data-invalid="true"] .label` 或 `[aria-invalid="true"] .label` — 无效状态样式(危险/红色文本)
**说明:** 必填星号会基于 role 与 `data-slot` 智能应用,并排除:
* `role="group"`、`role="radiogroup"`、`role="checkboxgroup"` 的元素
* `data-slot="radio"` 或 `data-slot="checkbox"` 的元素
从而在分组组件与必填字段组合时避免重复星号。
## API 参考
### Label
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ----------- | ------- | ----------- |
| `htmlFor` | `string` | - | 标签所关联元素的 id |
| `isRequired` | `boolean` | `false` | 是否显示必填指示符 |
| `isDisabled` | `boolean` | `false` | 标签是否处于禁用状态 |
| `isInvalid` | `boolean` | `false` | 标签是否处于无效状态 |
| `className` | `string` | - | 附加的 CSS 类 |
| `children` | `ReactNode` | - | 标签内容 |
## 无障碍
Label 基于原生 HTML ``([MDN 参考](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)),并遵循 WAI-ARIA 最佳实践:
* 使用 `htmlFor` 与表单控件关联
* 提供语义化的 `` 元素
* 与表单控件关联时支持键盘导航
* 向屏幕阅读器传达必填与无效状态
* 点击标签可聚焦/激活关联的表单控件
## 相关组件
## 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
# NumberField 数字输入框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/number-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/number-field.mdx
> 数字输入字段,包含增减按钮、校验与国际化格式化能力。
## 用法
```tsx
import { NumberField } from '@heroui/react';
```
```tsx
import {Label, NumberField} from "@heroui/react";
export function Basic() {
return (
宽度
);
}
```
## 组件结构
```tsx
import {NumberField, Label, Description, FieldError} from '@heroui/react';
export default () => (
)
```
> **NumberField** 允许用户输入数值,并可选择是否显示增减按钮。它支持国际化格式化、校验与键盘导航。
## 示例
### 变体
NumberField 组件支持两种视觉变体:
* **`primary`**(默认)— 带阴影的标准样式,适用于大多数场景
* **`secondary`** — 低强调、无阴影,适合在 Surface 等表面背景上使用
```tsx
import {Label, NumberField} from "@heroui/react";
export function Variants() {
return (
主要变体
次要变体
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"`,以应用适合表面背景的低强调变体。
```tsx
import {Description, Label, NumberField, Surface} from "@heroui/react";
export function OnSurface() {
return (
宽度
以像素为单位输入宽度
百分比
取值须在 0 到 100 之间
);
}
```
### 带描述
```tsx
import {Description, Label, NumberField} from "@heroui/react";
export function WithDescription() {
return (
宽度
以像素为单位输入宽度
百分比
取值须在 0 到 100 之间
);
}
```
### 必填字段
```tsx
import {Description, Label, NumberField} from "@heroui/react";
export function Required() {
return (
数量
评分
评分范围 1 到 10
);
}
```
### 禁用状态
```tsx
import {Description, Label, NumberField} from "@heroui/react";
export function Disabled() {
return (
宽度
以像素为单位输入宽度
百分比
取值须在 0 到 100 之间
);
}
```
### 宽度充满
```tsx
import {Label, NumberField} from "@heroui/react";
export function FullWidth() {
return (
宽度
);
}
```
### 表单校验
将 `isInvalid` 与 `FieldError` 配合使用,以展示校验消息。
```tsx
import {FieldError, Label, NumberField} from "@heroui/react";
export function Validation() {
return (
数量
数量必须大于或等于 0
百分比
百分比必须在 0 到 100 之间
);
}
```
### 受控组件
控制 value 以与其他组件同步或执行自定义格式化。
```tsx
"use client";
import {Button, Description, Label, NumberField} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState(1024);
return (
宽度
当前值:{value}
setValue(0)}>
重置为 0
setValue(2048)}>
设为 2048
);
}
```
### 步进值
配置增减步进值以实现精确控制。
```tsx
import {Description, Label, NumberField} from "@heroui/react";
export function WithStep() {
return (
步长:1
每次增减 1
步长:5
每次增减 5
步长:10
每次增减 10
);
}
```
### 格式化选项
支持将数字格式化为货币、百分比、小数或单位,并具备国际化能力。
```tsx
import {Description, Label, NumberField} from "@heroui/react";
export function WithFormatOptions() {
return (
货币(EUR - 会计格式)
欧元会计记账格式
货币(USD)
标准美元货币格式
百分比
百分比格式(0–1,0.5 表示 50%)
小数(保留 2 位)
保留 2 位小数格式
单位(千克)
千克单位格式
);
}
```
### 表单示例
完整的表单集成示例,包含校验与提交处理。
```tsx
"use client";
import {Button, Description, FieldError, Form, Label, NumberField, Spinner} from "@heroui/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.SyntheticEvent) => {
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 (
订购数量
{isOutOfStock ? (
仅剩 {STOCK_AVAILABLE} 件库存
) : (
仅剩 {STOCK_AVAILABLE} 件可购
)}
STOCK_AVAILABLE}
isPending={isSubmitting}
type="submit"
variant="primary"
>
{isSubmitting ? (
<>
处理中…
>
) : (
"下单"
)}
);
}
```
### 带校验
通过受控 value 实现自定义校验逻辑。
```tsx
"use client";
import {Description, FieldError, Label, NumberField} from "@heroui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState(undefined);
const isInvalid = value !== undefined && (value < 0 || value > 100);
return (
百分比
{isInvalid ? (
百分比必须在 0 到 100 之间
) : (
请输入 0 到 100 之间的值
)}
);
}
```
### 自定义图标
自定义增减按钮图标。
```tsx
import {Description, Label, NumberField} from "@heroui/react";
export function CustomIcons() {
return (
);
}
```
### 步进箭头
使用垂直布局的 chevron 图标,呈现不同的视觉风格。
```tsx
import {Label, NumberField} from "@heroui/react";
export function WithChevrons() {
return (
带 Chevron 的数字输入框
);
}
```
### 渲染函数
```tsx
"use client";
import {Label, NumberField} from "@heroui/react";
export function RenderFunction() {
return (
}
>
宽度
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Label, NumberField} from "@heroui/react";
export function CustomStyles() {
return (
宾客
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
#### 基础类 \[!toc]
* `.number-field` – 根容器,样式非常克制(`flex flex-col gap-1`)
* `.number-field__group` – 输入与按钮的容器,包含边框与背景样式
* `.number-field__input` – 数字输入字段
* `.number-field__increment-button` – 用于增加数值的按钮
* `.number-field__decrement-button` – 用于减少数值的按钮
#### 变体类 \[!toc]
* `.number-field--primary` – 带阴影的主变体(默认)
* `.number-field--secondary` – 无阴影的次变体,适合用在 surface 上
> **说明:** 子组件([Label](/docs/react/components/label)、[Description](/docs/react/components/description)、[FieldError](/docs/react/components/field-error))拥有各自的 CSS 类与样式。自定义方式请参见对应文档。
### 交互状态
NumberField 会根据状态自动管理以下 data 属性:
* **Invalid**:`[data-invalid="true"]` 或 `[aria-invalid="true"]` – 无效时会自动隐藏 description 插槽
* **Disabled**:`[data-disabled="true"]` – 当 `isDisabled` 为 true 时应用
* **Focus Within**:`[data-focus-within="true"]` – 当输入框或按钮聚焦时应用
* **Focus Visible**:`[data-focus-visible="true"]` – 当焦点可见(键盘导航)时应用
* **Hovered**:`[data-hovered="true"]` – 当悬停在按钮上时应用
更多属性可通过渲染 prop 获得(见下方的 NumberFieldRenderProps)。
## API 参考
### NumberField
NumberField 继承 React Aria [NumberField](https://react-spectrum.adobe.com/react-aria/NumberField.html) 组件的全部 props。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: NumberFieldRenderProps) => React.ReactNode` | - | 子组件(Label、Group、Input 等)或渲染函数。 |
| `className` | `string \| (values: NumberFieldRenderProps) => string` | - | 用于样式的 CSS 类,支持渲染 prop。 |
| `style` | `React.CSSProperties \| (values: NumberFieldRenderProps) => React.CSSProperties` | - | 行内样式,支持渲染 prop。 |
| `fullWidth` | `boolean` | `false` | 数字字段是否占满容器宽度 |
| `id` | `string` | - | 元素的唯一标识符。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 组件的视觉变体。`primary` 为默认带阴影样式。`secondary` 为低强调、无阴影变体,适合用在 surface 上。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
#### Value Props
| Prop | 类型 | 默认值 | 描述 |
| -------------- | -------------------------------------- | --- | -------------- |
| `value` | `number` | - | 当前值(受控)。 |
| `defaultValue` | `number` | - | 默认值(非受控)。 |
| `onChange` | `(value: number \| undefined) => void` | - | 值变化时触发的事件处理函数。 |
#### Formatting Props
| Prop | 类型 | 默认值 | 描述 |
| --------------- | -------------------------- | --- | ----------------------- |
| `formatOptions` | `Intl.NumberFormatOptions` | - | 数字格式化选项(货币、百分比、小数、单位等)。 |
| `locale` | `string` | - | 数字格式化的区域设置。 |
#### Validation Props
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ----------------------------------------------------------------- | ---------- | ------------------------ |
| `isRequired` | `boolean` | `false` | 提交表单前是否要求用户输入。 |
| `isInvalid` | `boolean` | - | 当前值是否无效。 |
| `validate` | `(value: number) => ValidationError \| true \| null \| undefined` | - | 自定义校验函数。 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 表单校验或 ARIA 属性。 |
| `validationErrors` | `string[]` | - | 服务端校验错误。 |
#### Range Props
| Prop | 类型 | 默认值 | 描述 |
| ---------- | -------- | --- | --------- |
| `minValue` | `number` | - | 允许的最小值。 |
| `maxValue` | `number` | - | 允许的最大值。 |
| `step` | `number` | `1` | 增减操作的步进值。 |
#### State Props
| Prop | 类型 | 默认值 | 描述 |
| ------------ | --------- | --- | ----------- |
| `isDisabled` | `boolean` | - | 是否禁用输入。 |
| `isReadOnly` | `boolean` | - | 是否可选中但不可修改。 |
#### Form Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------- | --- | ------------------------- |
| `name` | `string` | - | input 元素的名称,用于 HTML 表单提交。 |
| `autoFocus` | `boolean` | - | 元素渲染后是否应获得焦点。 |
#### Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------- | --- | -------------- |
| `aria-label` | `string` | - | 没有可见标签时的无障碍标签。 |
| `aria-labelledby` | `string` | - | 用于标注该字段的元素 ID。 |
| `aria-describedby` | `string` | - | 用于描述该字段的元素 ID。 |
| `aria-details` | `string` | - | 包含更多详情的元素 ID。 |
### Composition Components
NumberField 需要与以下独立组件组合使用,请分别导入并直接使用:
* **NumberField.Group** – 输入与按钮的容器
* **NumberField.Input** – 数字输入字段
* **NumberField.IncrementButton** – 用于增加数值的按钮
* **NumberField.DecrementButton** – 用于减少数值的按钮
* **Label** – 字段标签组件(`@heroui/react`)
* **Description** – 辅助说明文本组件(`@heroui/react`)
* **FieldError** – 校验错误信息组件(`@heroui/react`)
这些组件各自拥有 props API。请直接在 NumberField 内组合使用:
```tsx
Quantity
Enter a value between 0 and 100
Value must be between 0 and 100
```
#### NumberField.Group Props
NumberField.Group 继承 React Aria [Group](https://react-spectrum.adobe.com/react-aria/Group.html) 组件的 props。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------ | --- | ------------------------ |
| `children` | `React.ReactNode \| (values: GroupRenderProps) => React.ReactNode` | - | 子组件(Input、Buttons)或渲染函数。 |
| `className` | `string \| (values: GroupRenderProps) => string` | - | 用于样式的 CSS 类。 |
#### NumberField.Input Props
NumberField.Input 继承 React Aria [Input](https://react-spectrum.adobe.com/react-aria/Input.html) 组件的 props。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------- | ----------- | ----------------------------------------------------------------- |
| `className` | `string` | - | 用于样式的 CSS 类。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 输入的视觉变体。`primary` 为默认带阴影样式。`secondary` 为低强调、无阴影变体,适合用在 surface 上。 |
#### NumberField.IncrementButton Props
NumberField.IncrementButton 继承 React Aria [Button](https://react-spectrum.adobe.com/react-aria/Button.html) 组件的 props。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | -------------- | --------------------------- |
| `children` | `React.ReactNode` | ` ` | 按钮的图标或内容。默认为加号图标。 |
| `className` | `string` | - | 用于样式的 CSS 类。 |
| `slot` | `"increment"` | `"increment"` | 必须设置为 `"increment"`(会自动设置)。 |
#### NumberField.DecrementButton Props
NumberField.DecrementButton 继承 React Aria [Button](https://react-spectrum.adobe.com/react-aria/Button.html) 组件的 props。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --------------- | --------------------------- |
| `children` | `React.ReactNode` | ` ` | 按钮的图标或内容。默认为减号图标。 |
| `className` | `string` | - | 用于样式的 CSS 类。 |
| `slot` | `"decrement"` | `"decrement"` | 必须设置为 `"decrement"`(会自动设置)。 |
### NumberFieldRenderProps
在 `className`、`style` 或 `children` 上使用渲染 prop 时,可使用以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------------------- | -------------------------------- |
| `isDisabled` | `boolean` | 字段是否禁用。 |
| `isInvalid` | `boolean` | 字段当前是否无效。 |
| `isReadOnly` | `boolean` | 字段是否只读。 |
| `isRequired` | `boolean` | 字段是否必填。 |
| `isFocused` | `boolean` | 字段是否聚焦(已弃用,请使用 `isFocusWithin`)。 |
| `isFocusWithin` | `boolean` | 是否有任意子元素聚焦。 |
| `isFocusVisible` | `boolean` | 是否为可见焦点(键盘导航)。 |
| `value` | `number \| undefined` | 当前值。 |
| `minValue` | `number \| undefined` | 允许的最小值。 |
| `maxValue` | `number \| undefined` | 允许的最大值。 |
| `step` | `number` | 增减步进值。 |
## 相关案例
## 相关组件
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **FieldError**: Inline validation messages for form fields
# RadioGroup 单选框组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/radio-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/radio-group.mdx
> 用于从列表中选择单个选项的单选组。
## 用法
```tsx
import { RadioGroup, Radio } from '@heroui/react';
```
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
export function Basic() {
return (
选择套餐
选择最适合你的套餐
基础版
每月包含 100 条消息
高级版
每月包含 200 条消息
商业版
无限消息
);
}
```
## 组件结构
导入 RadioGroup 组件后,可通过点号访问各个子部分。
```tsx
import {RadioGroup, Radio, Label, Description, FieldError} from '@heroui/react';
export default () => (
{/* 可点击区域:control + label */}
✓ {/* Custom indicator (optional) */}
Label {/* 纯文本 —— 可点击的标签 */}
{/* 兄弟节点 — 位于按钮外部(通过 aria-describedby 关联) */}
{/* 可选 — 单选项校验错误信息 */}
{/* 可选 — 组级校验 */}
)
```
## 示例
### 水平排列
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
export function Horizontal() {
return (
订阅套餐
入门版
适合副项目
专业版
高级报表
团队版
最多 10 名队友
);
}
```
### 变体
RadioGroup 组件支持两种视觉变体:
* **`primary`**(默认)— 带默认背景的标准样式,适用于大多数场景
* **`secondary`** — 低强调变体,适合在 Surface 等表面背景上使用
```tsx
import {Description, Radio, RadioGroup} from "@heroui/react";
export function Variants() {
return (
主要变体
选项 1
默认背景的标准样式
选项 2
另一种主要样式选项
次要变体
选项 1
用于表面上的低强调变体
选项 2
另一种次要样式选项
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"`,以应用适合表面背景的低强调变体。
```tsx
import {Description, Label, Radio, RadioGroup, Surface} from "@heroui/react";
export function OnSurface() {
return (
选择套餐
选择最适合你的套餐
基础版
每月包含 100 条消息
高级版
每月包含 200 条消息
商业版
无限消息
);
}
```
### 禁用
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
export function Disabled() {
return (
订阅套餐
我们正在发布更新,暂时无法更改套餐。
入门版
适合副项目和小型团队
专业版
高级报表与分析
团队版
最多可与 10 名队友共享访问权限
);
}
```
### 受控组件
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("pro");
return (
订阅套餐
入门版
适合副项目和小型团队
专业版
高级报表与分析
团队版
最多可与 10 名队友共享访问权限
已选套餐: {value}
);
}
```
### 非受控
仅需响应更新时,将 `defaultValue` 与 `onChange` 配合使用。
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
import React from "react";
export function Uncontrolled() {
const [selection, setSelection] = React.useState("pro");
return (
setSelection(nextValue)}
>
订阅套餐
入门版
适合副项目和小型团队
专业版
高级报表与分析
团队版
最多可与 10 名队友共享访问权限
上次选择的套餐: {selection}
);
}
```
### 表单校验
```tsx
"use client";
import {Button, Description, FieldError, Form, Label, Radio, RadioGroup} from "@heroui/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(`你选择的套餐是: ${value}`);
}}
>
订阅套餐
入门版
适合副项目和小型团队
专业版
高级报表与分析
团队版
最多可与 10 名队友共享访问权限
请先选择订阅套餐再继续。
Submit
{!!message && {message}
}
);
}
```
### 配送与支付
### 自定义指示器
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
export function CustomIndicator() {
return (
选择套餐
选择最适合你的套餐
{({isSelected}) =>
isSelected ? ✓ : null
}
基础版
每月包含 100 条消息
{({isSelected}) =>
isSelected ? ✓ : null
}
高级版
每月包含 200 条消息
{({isSelected}) =>
isSelected ? ✓ : null
}
商业版
无限消息
);
}
```
### 渲染函数
```tsx
"use client";
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
export function RenderFunction() {
return (
}
>
选择套餐
选择最适合你的套餐
基础版
每月包含 100 条消息
高级版
每月包含 200 条消息
商业版
无限消息
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Description, Label, Radio, RadioGroup} from "@heroui/react";
const options = [
{description: "每月扣费 $12", label: "按月", value: "monthly"},
{description: "每年一次性扣费 $120", label: "按年", value: "yearly"},
] as const;
const contentClassName =
"group flex w-full items-start gap-3 rounded-xl border border-success/10 bg-success-soft/30 px-4 py-3 transition-colors data-[focus-visible=true]:ring-2 data-[focus-visible=true]:ring-success/15 data-[hovered=true]:bg-success-soft-hover data-[selected=true]:border-success/30 data-[selected=true]:bg-success-soft data-[selected=true]:data-[hovered=true]:bg-success-soft-hover";
const controlClassName =
"mt-0.5 size-5 shrink-0 rounded-full border border-border bg-default shadow-none group-data-[pressed=true]:scale-95 group-data-[selected=true]:border-transparent group-data-[selected=true]:bg-success group-data-[selected=true]:group-data-[pressed=true]:bg-success-hover";
const indicatorClassName =
"before:rounded-full before:bg-default group-data-[selected=true]:before:scale-50 group-data-[selected=true]:before:bg-success-foreground group-data-[selected=true]:group-data-[pressed=true]:before:scale-[0.57]";
export function CustomStyles() {
return (
计费周期
选择你的扣费频率。
{options.map(({description, label, value}) => (
{label}
{description}
))}
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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-accent;
}
.radio__indicator {
@apply bg-primary;
}
.radio__content {
@apply gap-1;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
RadioGroup 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/radio-group.css)):
#### 基础类 \[!toc]
* `.radio-group` - 单选组基础容器
* `.radio` - 单个单选项
* `.radio__control` - 单选控件(圆形按钮)
* `.radio__indicator` - 单选指示器(内部圆点)
* `.radio__content` - 单选内容包裹层
#### 修饰类 \[!toc]
禁用态通过 `.radio` 上的 `[data-disabled="true"]` / `[aria-disabled="true"]`(以及 `:disabled`)应用——没有 `.radio--disabled` 修饰类。
### 交互状态
单选项同时支持 CSS 伪类与 data 属性,便于灵活定制:
* **Selected**:`[aria-checked="true"]` 或 `[data-selected="true"]`(显示指示器)
* **Hover**:`:hover` 或 `[data-hovered="true"]`(边框颜色变化)
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]`(显示焦点环)
* **Pressed**:`:active` 或 `[data-pressed="true"]`(缩放变换)
* **Disabled**:`:disabled` 或 `[aria-disabled="true"]`(降低透明度并禁用指针事件)
* **Invalid**:`[data-invalid="true"]` 或 `[aria-invalid="true"]`(错误边框颜色)
## API 参考
### RadioGroup
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ----------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------- |
| `value` | `string` | - | 当前值(受控) |
| `defaultValue` | `string` | - | 默认值(非受控) |
| `onChange` | `(value: string) => void` | - | 值变化时触发的事件处理函数 |
| `isDisabled` | `boolean` | `false` | 是否禁用整个单选组 |
| `isRequired` | `boolean` | `false` | 是否必填 |
| `isReadOnly` | `boolean` | `false` | 是否只读 |
| `isInvalid` | `boolean` | `false` | 是否处于无效状态 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 组件的视觉变体。`primary` 为默认带阴影样式。`secondary` 为低强调、无阴影变体,适合用在 surface 上。 |
| `name` | `string` | - | 单选组的名称,用于提交 HTML 表单 |
| `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | 单选组的排列方向 |
| `children` | `React.ReactNode \| (values: RadioGroupRenderProps) => React.ReactNode` | - | 单选组内容或渲染 prop |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Radio
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ----------------------------------------------------------------------------- | ------- | --------------------- |
| `value` | `string` | - | 单选项的值 |
| `isDisabled` | `boolean` | `false` | 是否禁用该单选项 |
| `name` | `string` | - | 单选项名称,用于提交 HTML 表单 |
| `children` | `React.ReactNode \| (values: RadioFieldRenderProps) => React.ReactNode` | - | 单选内容或字段级渲染 prop |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Radio.Control
继承 `React.HTMLAttributes`。
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ----------------- | --- | --------------------------------- |
| `children` | `React.ReactNode` | - | 控件包裹层内要渲染的内容(通常为 Radio.Indicator) |
### Radio.Indicator
继承 `React.HTMLAttributes`。
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ------------------------------------------------------------------------ | --- | ------------------------ |
| `children` | `React.ReactNode \| (values: RadioButtonRenderProps) => React.ReactNode` | - | 可选内容或接收当前单选按钮状态的渲染 prop。 |
### Radio.Content
单选项的可点击区域(包裹隐藏 input 的 ``)。请将 `Radio.Control` 与 `Label` 放在其中。`className` 支持接收 `RadioButtonRenderProps` 的渲染函数。
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ------------------------------------------------------------------------ | --- | -------------------------------- |
| `children` | `React.ReactNode \| (values: RadioButtonRenderProps) => React.ReactNode` | - | 可点击内容(通常为 Radio.Control 与 Label) |
### RadioFieldRenderProps
在根级 `Radio` 上使用渲染 prop 时,会提供以下字段级值:
| Prop | 类型 | 描述 |
| ------------ | --------- | -------- |
| `isSelected` | `boolean` | 单选项是否已选中 |
| `isDisabled` | `boolean` | 是否禁用 |
| `isReadOnly` | `boolean` | 是否只读 |
| `isInvalid` | `boolean` | 是否无效 |
| `isRequired` | `boolean` | 是否必填 |
### RadioButtonRenderProps
`Radio.Control` 和 `Radio.Indicator` 使用按钮级渲染 prop(`isHovered`、`isPressed`、`isFocusVisible` 等)。将函数作为 `Radio.Control` 子节点或传给 `Radio.Indicator` 即可访问它们。
## 相关案例
## 相关组件
## Related Components
* **Fieldset**: Group related form controls with legends
* **Surface**: Base container surface
* **Description**: Helper text for form fields
# SearchField 搜索框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/search-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/search-field.mdx
> 搜索输入字段,包含清除按钮与搜索图标。
## 用法
```tsx
import { SearchField } from '@heroui/react';
```
```tsx
import {Label, SearchField} from "@heroui/react";
export function Basic() {
return (
搜索
);
}
```
## 组件结构
```tsx
import {SearchField, Label, Description, FieldError} from '@heroui/react';
export default () => (
)
```
> **SearchField** 允许用户输入并清空搜索关键词。它包含搜索图标,并提供可选的清除按钮以便快速重置。
## 示例
### 变体
SearchField 组件支持两种视觉变体:
* **`primary`**(默认)— 带阴影的标准样式,适用于大多数场景
* **`secondary`** — 低强调、无阴影,适合在 Surface 等表面背景上使用
```tsx
import {Label, SearchField} from "@heroui/react";
export function Variants() {
return (
主要变体
次要变体
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"`,以应用适合表面背景的低强调变体。
```tsx
import {Description, Label, SearchField, Surface} from "@heroui/react";
export function OnSurface() {
return (
搜索
输入关键词进行搜索
高级搜索
使用筛选条件细化搜索
);
}
```
### 带描述
```tsx
import {Description, Label, SearchField} from "@heroui/react";
export function WithDescription() {
return (
搜索产品
输入关键词进行搜索 for products
搜索用户
按姓名、邮箱或用户名搜索
);
}
```
### 必填字段
```tsx
import {Description, Label, SearchField} from "@heroui/react";
export function Required() {
return (
搜索
搜索内容
至少需要 3 个字符
);
}
```
### 禁用状态
```tsx
import {Description, Label, SearchField} from "@heroui/react";
export function Disabled() {
return (
搜索
此搜索框已禁用
搜索
此搜索框已禁用
);
}
```
### 宽度充满
```tsx
import {Label, SearchField} from "@heroui/react";
export function FullWidth() {
return (
搜索
);
}
```
### 表单校验
将 `isInvalid` 与 `FieldError` 配合使用,以展示校验消息。
```tsx
import {FieldError, Label, SearchField} from "@heroui/react";
export function Validation() {
return (
搜索
搜索内容至少需要 3 个字符
搜索
搜索内容包含无效字符
);
}
```
### 受控组件
控制 value 以与其他组件同步或执行自定义格式化。
```tsx
"use client";
import {Button, Description, Label, SearchField} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("");
return (
搜索
当前值: {value || "(空)"}
setValue("")}>
Clear
setValue("示例查询")}>
设置示例
);
}
```
### 表单示例
完整的表单集成示例,包含校验与提交处理。
```tsx
"use client";
import {Button, Description, FieldError, Form, Label, SearchField, Spinner} from "@heroui/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.SyntheticEvent) => {
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 (
搜索产品
{isInvalid ? (
搜索内容至少需要 {MIN_LENGTH} 个字符
) : (
请输入至少 {MIN_LENGTH} 个字符后再搜索
)}
{isSubmitting ? (
<>
搜索中…
>
) : (
"搜索"
)}
);
}
```
### 带校验
通过受控 value 实现自定义校验逻辑。
```tsx
"use client";
import {Description, FieldError, Label, SearchField} from "@heroui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState("");
const isInvalid = value.length > 0 && value.length < 3;
return (
搜索
{isInvalid ? (
搜索内容至少需要 3 个字符
) : (
请输入至少 3 个字符后再搜索
)}
);
}
```
### 自定义图标
自定义搜索图标与清除按钮图标。
```tsx
import {Description, Label, SearchField} from "@heroui/react";
export function CustomIcons() {
return (
);
}
```
### 键盘快捷键
添加快捷键以快速聚焦搜索框。
```tsx
"use client";
import {Description, Kbd, Label, SearchField} from "@heroui/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 (
搜索
使用键盘快捷键快速聚焦此输入框
按
S
聚焦搜索框
);
}
```
### 渲染函数
```tsx
"use client";
import {Label, SearchField} from "@heroui/react";
export function RenderFunction() {
return (
}>
搜索
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Label, SearchField} from "@heroui/react";
export function CustomStyles() {
return (
搜索文档
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
#### 基础类 \[!toc]
* `.search-field` – 根容器,样式非常克制(`flex flex-col gap-1`)
* `.search-field__group` – 搜索图标、输入框与清除按钮的容器,包含边框与背景样式
* `.search-field__input` – 搜索输入字段
* `.search-field__search-icon` – 左侧显示的搜索图标
* `.search-field__clear-button` – 用于清空搜索字段的按钮
#### 变体类 \[!toc]
* `.search-field--primary` – 带阴影的主变体(默认)
* `.search-field--secondary` – 无阴影的次变体,适合用在 surface 上
> **说明:** 子组件([Label](/docs/react/components/label)、[Description](/docs/react/components/description)、[FieldError](/docs/react/components/field-error))拥有各自的 CSS 类与样式。自定义方式请参见对应文档。
### 交互状态
SearchField 会根据状态自动管理以下 data 属性:
* **Invalid**:`[data-invalid="true"]` 或 `[aria-invalid="true"]` – 无效时会自动隐藏 description 插槽
* **Disabled**:`[data-disabled="true"]` – 当 `isDisabled` 为 true 时应用
* **Focus Within**:`[data-focus-within="true"]` – 当输入框聚焦时应用
* **Focus Visible**:`[data-focus-visible="true"]` – 当焦点可见(键盘导航)时应用
* **Hovered**:`[data-hovered="true"]` – 当悬停在整个组合上时应用
* **Empty**:`[data-empty="true"]` – 当字段为空时应用(会隐藏清除按钮)
更多属性可通过渲染 prop 获得(见下方的 SearchFieldRenderProps)。
## API 参考
### SearchField
SearchField 继承 React Aria [SearchField](https://react-spectrum.adobe.com/react-aria/SearchField.html) 组件的全部 props。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------- |
| `children` | `React.ReactNode \| (values: SearchFieldRenderProps) => React.ReactNode` | - | 子组件(Label、Group、Input 等)或渲染函数。 |
| `className` | `string \| (values: SearchFieldRenderProps) => string` | - | 用于样式的 CSS 类,支持渲染 prop。 |
| `style` | `React.CSSProperties \| (values: SearchFieldRenderProps) => React.CSSProperties` | - | 行内样式,支持渲染 prop。 |
| `fullWidth` | `boolean` | `false` | 搜索字段是否占满容器宽度 |
| `id` | `string` | - | 元素的唯一标识符。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 组件的视觉变体。`primary` 为默认带阴影样式。`secondary` 为低强调、无阴影变体,适合用在 surface 上。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
#### Value Props
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ------------------------- | --- | -------------- |
| `value` | `string` | - | 当前值(受控)。 |
| `defaultValue` | `string` | - | 默认值(非受控)。 |
| `onChange` | `(value: string) => void` | - | 值变化时触发的事件处理函数。 |
#### Validation Props
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ----------------------------------------------------------------- | ---------- | ------------------------ |
| `isRequired` | `boolean` | `false` | 提交表单前是否要求用户输入。 |
| `isInvalid` | `boolean` | - | 当前值是否无效。 |
| `validate` | `(value: string) => ValidationError \| true \| null \| undefined` | - | 自定义校验函数。 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 表单校验或 ARIA 属性。 |
| `validationErrors` | `string[]` | - | 服务端校验错误。 |
#### State Props
| Prop | 类型 | 默认值 | 描述 |
| ------------ | --------- | --- | ----------- |
| `isDisabled` | `boolean` | - | 是否禁用输入。 |
| `isReadOnly` | `boolean` | - | 是否可选中但不可修改。 |
#### Form Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------- | --- | ------------------------- |
| `name` | `string` | - | input 元素的名称,用于 HTML 表单提交。 |
| `autoFocus` | `boolean` | - | 元素渲染后是否应获得焦点。 |
#### Event Props
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ------------------------- | --- | ------------------------ |
| `onSubmit` | `(value: string) => void` | - | 用户提交搜索(Enter)时触发的事件处理函数。 |
| `onClear` | `() => void` | - | 按下清除按钮时触发的事件处理函数。 |
#### Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------- | --- | -------------- |
| `aria-label` | `string` | - | 没有可见标签时的无障碍标签。 |
| `aria-labelledby` | `string` | - | 用于标注该字段的元素 ID。 |
| `aria-describedby` | `string` | - | 用于描述该字段的元素 ID。 |
| `aria-details` | `string` | - | 包含更多详情的元素 ID。 |
### Composition Components
SearchField 需要与以下独立组件组合使用,请分别导入并直接使用:
* **SearchField.Group** – 搜索图标、输入框与清除按钮的容器
* **SearchField.Input** – 搜索输入字段
* **SearchField.SearchIcon** – 左侧显示的搜索图标
* **SearchField.ClearButton** – 用于清空搜索字段的按钮
* **Label** – 字段标签组件(`@heroui/react`)
* **Description** – 辅助说明文本组件(`@heroui/react`)
* **FieldError** – 校验错误信息组件(`@heroui/react`)
这些组件各自拥有 props API。请直接在 SearchField 内组合使用:
```tsx
Search
Enter keywords to search
Search query is required
```
#### SearchField.Group Props
SearchField.Group 继承 React Aria [Group](https://react-spectrum.adobe.com/react-aria/Group.html) 组件的 props。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------ | --- | --------------------------------------- |
| `children` | `React.ReactNode \| (values: GroupRenderProps) => React.ReactNode` | - | 子组件(SearchIcon、Input、ClearButton)或渲染函数。 |
| `className` | `string \| (values: GroupRenderProps) => string` | - | 用于样式的 CSS 类。 |
#### SearchField.Input Props
SearchField.Input 继承 React Aria [Input](https://react-spectrum.adobe.com/react-aria/Input.html) 组件的 props。
| Prop | 类型 | 默认值 | 描述 |
| ------------- | -------------------------- | ----------- | ----------------------------------------------------------------- |
| `className` | `string` | - | 用于样式的 CSS 类。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 输入的视觉变体。`primary` 为默认带阴影样式。`secondary` 为低强调、无阴影变体,适合用在 surface 上。 |
| `placeholder` | `string` | - | 输入为空时显示的占位符文本。 |
| `type` | `string` | `"search"` | 输入类型(会自动设置为 `"search"`)。 |
#### SearchField.SearchIcon Props
SearchField.SearchIcon 是一个用于渲染搜索图标的自定义组件。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | ---------------- | ---------------- |
| `children` | `React.ReactNode` | ` ` | 自定义图标元素。默认为搜索图标。 |
| `className` | `string` | - | 用于样式的 CSS 类。 |
#### SearchField.ClearButton Props
SearchField.ClearButton 继承 React Aria [Button](https://react-spectrum.adobe.com/react-aria/Button.html) 组件的 props。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | ---------------------- | ----------------------- |
| `children` | `React.ReactNode` | ` ` | 按钮的图标或内容。默认为关闭图标。 |
| `className` | `string` | - | 用于样式的 CSS 类。 |
| `slot` | `"clear"` | `"clear"` | 必须设置为 `"clear"`(会自动设置)。 |
### SearchFieldRenderProps
在 `className`、`style` 或 `children` 上使用渲染 prop 时,可使用以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | -------------------------------- |
| `isDisabled` | `boolean` | 字段是否禁用。 |
| `isInvalid` | `boolean` | 字段当前是否无效。 |
| `isReadOnly` | `boolean` | 字段是否只读。 |
| `isRequired` | `boolean` | 字段是否必填。 |
| `isFocused` | `boolean` | 字段是否聚焦(已弃用,请使用 `isFocusWithin`)。 |
| `isFocusWithin` | `boolean` | 是否有任意子元素聚焦。 |
| `isFocusVisible` | `boolean` | 是否为可见焦点(键盘导航)。 |
| `value` | `string` | 当前值。 |
| `isEmpty` | `boolean` | 字段是否为空。 |
## 相关案例
## 相关组件
## Related Components
* **Label**: Accessible label for form controls
* **Description**: Helper text for form fields
* **FieldError**: Inline validation messages for form fields
# TextArea 多行文本框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/text-area
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/text-area.mdx
> 原语级多行文本输入组件,可接受标准 HTML 属性。
## 用法
```tsx
import { TextArea } from '@heroui/react';
```
```tsx
import {TextArea} from "@heroui/react";
export function Basic() {
return (
);
}
```
校验、标签与错误消息请参阅 **[TextField](/docs/react/components/text-field)**。
## 示例
### 变体
TextArea 组件支持两种视觉变体:
* **`primary`**(默认)— 带阴影的标准样式,适用于大多数场景
* **`secondary`** — 低强调、无阴影,适合在 Surface 等表面背景上使用
```tsx
import {TextArea} from "@heroui/react";
export function Variants() {
return (
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"`,以应用适合表面背景的低强调变体。
```tsx
import {Surface, TextArea} from "@heroui/react";
export function OnSurface() {
return (
);
}
```
### 宽度充满
```tsx
import {TextArea} from "@heroui/react";
export function FullWidth() {
return (
);
}
```
### 受控组件
```tsx
"use client";
import {Description, TextArea} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("");
return (
setValue(event.target.value)}
/>
字符数: {value.length} / 280
);
}
```
### 行数与尺寸调整
```tsx
import {Label, TextArea} from "@heroui/react";
export function Rows() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {TextArea} from "@heroui/react";
const fieldClass =
"rounded-xl border border-border/80 bg-surface shadow-sm ring-1 ring-black/5 transition-[box-shadow,border-color] focus-visible:ring-2 focus-visible:ring-neutral-400/25 dark:ring-white/10 dark:focus-visible:ring-neutral-500/30";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.textarea {
@apply rounded-xl border border-border bg-surface 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-accent ring-2 ring-accent/20;
}
&[data-invalid="true"] {
@apply border-danger bg-danger-soft text-danger;
}
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
#### 基础类 \[!toc]
* `.textarea` – 底层 `` 元素样式
### 交互状态
* **悬停**:`:hover` 或 `[data-hovered="true"]`
* **可见焦点**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **无效**:`[data-invalid="true"]`
* **禁用**:`:disabled` 或 `[aria-disabled="true"]`
## API 参考
### TextArea
TextArea 接受所有标准 HTML `` 属性,以及以下属性:
| Prop | 类型 | 默认值 | 描述 |
| -------------- | --------------------------------------------------------- | ----------- | ----------------------------------------------------------------- |
| `className` | `string` | - | 与基础样式合并的 Tailwind 类。 |
| `rows` | `number` | `3` | 可见文本行数。 |
| `cols` | `number` | - | 文本控件的可见宽度。 |
| `value` | `string` | - | TextArea 的受控值。 |
| `defaultValue` | `string` | - | 非受控初始值。 |
| `onChange` | `(event: React.ChangeEvent) => void` | - | 变更处理函数。 |
| `placeholder` | `string` | - | 占位符文本。 |
| `disabled` | `boolean` | `false` | 禁用 TextArea。 |
| `readOnly` | `boolean` | `false` | 将 TextArea 设为只读。 |
| `required` | `boolean` | `false` | 将 TextArea 标记为必填。 |
| `name` | `string` | - | 表单提交时使用的 name。 |
| `autoComplete` | `string` | - | 浏览器自动完成提示。 |
| `maxLength` | `number` | - | 最大字符数。 |
| `minLength` | `number` | - | 最小字符数。 |
| `wrap` | `'soft' \| 'hard'` | - | 提交时文本如何换行。 |
| `fullWidth` | `boolean` | `false` | TextArea 是否占满容器宽度 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 组件的视觉变体。`primary` 为默认带阴影样式。`secondary` 为弱强调、无阴影变体,适合用于 surface 上。 |
> 对于 `isInvalid`、`isRequired` 等校验 prop 以及错误处理,请将 TextArea 作为子组件与 **[TextField](/docs/react/components/text-field)** 一起使用。
## 相关案例
## 相关组件
## 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
# TextField 文本输入框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/text-field
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(forms)/text-field.mdx
> 便于组合的文本字段,包含标签、说明与内联校验。
## 用法
```tsx
import { TextField } from '@heroui/react';
```
```tsx
import {Input, Label, TextField} from "@heroui/react";
export function Basic() {
return (
邮箱
);
}
```
## 组件结构
```tsx
import {TextField, Label, Input, Description, FieldError} from '@heroui/react';
export default () => (
)
```
> **TextField** 将标签、输入、说明与错误信息整合为单个无障碍组件。若只需独立输入,请使用 **[Input](/docs/react/components/input)** 或 **[TextArea](/docs/react/components/text-area)**。
## 示例
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请在 Input 或 TextArea 组件上使用 `variant="secondary"`,以应用适合表面背景的低强调变体。
```tsx
import {Description, Input, Label, Surface, TextArea, TextField} from "@heroui/react";
export function OnSurface() {
return (
你的姓名
我们绝不会与他人分享此信息
邮箱
个人简介
至少 4 行
);
}
```
### 带描述
```tsx
import {Description, Input, Label, TextField} from "@heroui/react";
export function WithDescription() {
return (
用户名
为你的账户选择一个唯一的用户名
);
}
```
### 必填字段
```tsx
import {Description, Input, Label, TextField} from "@heroui/react";
export function Required() {
return (
全名
此字段为必填项
);
}
```
### 禁用状态
```tsx
import {Description, Input, Label, TextField} from "@heroui/react";
export function Disabled() {
return (
账户 ID
此字段不可编辑
);
}
```
### 宽度充满
```tsx
import {FieldError, Input, Label, TextField} from "@heroui/react";
export function FullWidth() {
return (
你的姓名
密码
密码长度必须超过 8 个字符
);
}
```
### 表单校验
将 `isInvalid` 与 `FieldError` 配合使用,以展示校验消息。
```tsx
"use client";
import {Description, FieldError, Input, Label, TextArea, TextField} from "@heroui/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 (
用户名
{isUsernameInvalid ? (
用户名至少需要 3 个字符。
) : (
为你的资料选择一个唯一的用户名。
)}
个人简介
{isBioInvalid ? (
个人简介至少需要 20 个字符。
) : (
至少 20 个字符 ({bio.length}/20).
)}
);
}
```
### 受控组件
控制 value 以同步计数器、预览或格式化逻辑。
```tsx
"use client";
import {Description, Input, Label, TextArea, TextField} from "@heroui/react";
import React from "react";
export function Controlled() {
const [name, setName] = React.useState("");
const [bio, setBio] = React.useState("");
return (
显示名称
字符数: {name.length}
个人简介
字符数: {bio.length} / 200
);
}
```
### 错误信息
```tsx
import {FieldError, Input, Label, TextField} from "@heroui/react";
export function WithError() {
return (
邮箱
请输入有效的邮箱地址
);
}
```
### 文本域
多行内容请使用 [TextArea](/docs/react/components/text-area),而非 [Input](/docs/react/components/input)。
```tsx
import {Description, Label, TextArea, TextField} from "@heroui/react";
export function TextAreaExample() {
return (
消息
最多 500 个字符
);
}
```
### Input 类型
```tsx
import {Input, Label, TextField} from "@heroui/react";
export function InputTypes() {
return (
密码
年龄
邮箱
网站
电话
);
}
```
### 渲染函数
```tsx
"use client";
import {Input, Label, TextField} from "@heroui/react";
export function RenderFunction() {
return (
}
type="email"
>
邮箱
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Input, Label, TextField} from "@heroui/react";
const fieldClass =
"rounded-xl border border-border/80 bg-surface shadow-sm ring-1 ring-black/5 transition-[box-shadow,border-color] focus-visible:ring-2 focus-visible:ring-neutral-400/25 dark:ring-white/10 dark:focus-visible:ring-neutral-500/30";
export function CustomStyles() {
return (
邮箱
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
* `.textfield` – 根容器,样式极少(`flex flex-col gap-1`)
> **提示:** 子组件([Label](/docs/react/components/label)、[Input](/docs/react/components/input)、[TextArea](/docs/react/components/text-area)、[Description](/docs/react/components/description)、[FieldError](/docs/react/components/field-error))各自拥有 CSS 类与样式,定制方式请参见对应文档。
### 交互状态
TextField 会根据状态自动管理以下 data 属性:
* **无效**:`[data-invalid="true"]` 或 `[aria-invalid="true"]` — 无效时自动隐藏 description 插槽
* **禁用**:`[data-disabled="true"]` — 在 `isDisabled` 为 true 时应用
* **焦点在内部**:`[data-focus-within="true"]` — 任一子级 input 聚焦时应用
* **可见焦点**:`[data-focus-visible="true"]` — 键盘导航产生可见焦点时应用
更多属性可通过 render prop 获取(见下文 TextFieldRenderProps)。
## API 参考
### TextField
继承 React Aria [TextField](https://react-spectrum.adobe.com/react-aria/TextField.html) 的全部 props。
#### Base Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------ | ------- | ------------------------ |
| `children` | `React.ReactNode \| (values: TextFieldRenderProps) => React.ReactNode` | - | 子组件(Label、Input 等)或渲染函数。 |
| `className` | `string \| (values: TextFieldRenderProps) => string` | - | 用于样式的 CSS 类,支持渲染 prop。 |
| `style` | `React.CSSProperties \| (values: TextFieldRenderProps) => React.CSSProperties` | - | 行内样式,支持渲染 prop。 |
| `fullWidth` | `boolean` | `false` | TextField 是否占满容器宽度。 |
| `id` | `string` | - | 元素的唯一 id。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
#### Validation Props
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ----------------------------------------------------------------- | ---------- | ------------------------- |
| `isRequired` | `boolean` | `false` | 提交表单前是否必须填写。 |
| `isInvalid` | `boolean` | - | 当前值是否无效。 |
| `validate` | `(value: string) => ValidationError \| true \| null \| undefined` | - | 自定义校验函数。 |
| `validationBehavior` | `'native' \| 'aria'` | `'native'` | 使用原生 HTML 表单校验还是 ARIA 属性。 |
| `validationErrors` | `string[]` | - | 服务端校验错误。 |
#### Value Props
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ------------------------- | --- | -------------- |
| `value` | `string` | - | 当前值(受控)。 |
| `defaultValue` | `string` | - | 默认值(非受控)。 |
| `onChange` | `(value: string) => void` | - | 值变化时调用的事件处理函数。 |
#### State Props
| Prop | 类型 | 默认值 | 描述 |
| ------------ | --------- | --- | ----------- |
| `isDisabled` | `boolean` | - | 是否禁用输入。 |
| `isReadOnly` | `boolean` | - | 是否可选中但不可修改。 |
#### Form Props
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------- | --- | ----------------------- |
| `name` | `string` | - | 用于 HTML 表单提交的 input 名称。 |
| `autoFocus` | `boolean` | - | 是否在挂载时自动聚焦。 |
#### Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------- | --- | -------------- |
| `aria-label` | `string` | - | 无可见标签时的无障碍标签。 |
| `aria-labelledby` | `string` | - | 用于标注该字段的元素 id。 |
| `aria-describedby` | `string` | - | 用于描述该字段的元素 id。 |
| `aria-details` | `string` | - | 提供附加详情的元素 id。 |
### Composition Components
TextField 与以下独立组件配合使用,请直接按需引入并组合:
* **Label** — `@heroui/react` 的字段标签组件
* **Input** — `@heroui/react` 的单行文本输入
* **TextArea** — `@heroui/react` 的多行文本输入
* **Description** — `@heroui/react` 的辅助说明组件
* **FieldError** — `@heroui/react` 的校验错误信息组件
这些组件各自有独立的 props API,请在 TextField 内直接使用:
```tsx
Email Address
setEmail(e.target.value)} />
We'll never share your email.
Please enter a valid email address.
```
### TextFieldRenderProps
对 `className`、`style` 或 `children` 使用渲染 prop 时,可使用以下值:
| Prop | 类型 | 描述 |
| ---------------- | --------- | ---------------------------------- |
| `isDisabled` | `boolean` | 字段是否禁用。 |
| `isInvalid` | `boolean` | 字段当前是否无效。 |
| `isReadOnly` | `boolean` | 字段是否只读。 |
| `isRequired` | `boolean` | 字段是否必填。 |
| `isFocused` | `boolean` | 字段是否聚焦(已弃用 — 请使用 `isFocusWithin`)。 |
| `isFocusWithin` | `boolean` | 是否有任一子元素聚焦。 |
| `isFocusVisible` | `boolean` | 是否为可见键盘焦点。 |
## 相关案例
## 相关组件
## 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
# Card 卡片
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/card
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(layout)/card.mdx
> 灵活容器组件,用于分组相关内容与操作
## 用法
```tsx
import { Card } from "@heroui/react";
```
```tsx
import {CircleDollar} from "@gravity-ui/icons";
import {Card, Link} from "@heroui/react";
export function Default() {
return (
成为 Acme 创作者!
前往 Acme 创作者中心立即注册,开始从粉丝与支持者处获得积分奖励。
创作者中心
);
}
```
## 组件结构
```tsx
import { Card } from "@heroui/react";
export default () => (
);
```
## 示例
### 变体
Card 提供语义变体,描述 prominence 级别而非具体视觉样式,允许主题以不同方式解读:
```tsx
import {Card} from "@heroui/react";
export function Variants() {
return (
透明
背景透明,视觉层级较低(transparent)
适合次要内容或嵌套在其它容器中的卡片
默认
标准外观(bg-surface)
大多数场景的默认卡片变体
次要
中等强调(bg-surface-secondary)
用于需要适度吸引注意力的内容
第三
更高强调(bg-surface-tertiary)
适合主要内容或需要突出的展示位
);
}
```
* **`transparent`** - 最低 prominence,透明背景(适合嵌套卡片)
* **`default`** - 大多数场景的标准卡片(surface-secondary)
* **`secondary`** - 中等 prominence 以吸引适度注意(surface-tertiary)
* **`tertiary`** - 更高 prominence 用于重要内容(surface-tertiary)
### 水平布局
```tsx
import {Button, Card, CloseButton} from "@heroui/react";
export function Horizontal() {
return (
成为 ACME 创作者!
这是一段占位说明文字,用于展示横向卡片布局、配图与右上角关闭按钮的排版效果。
仅剩 10 个名额
报名截止:10 月 10 日
立即申请
);
}
```
### 带头像
```tsx
import {Avatar, Card} from "@heroui/react";
export function WithAvatar() {
return (
Indie Hackers
148 位成员
IH
创建者:玛莎
AI Builders
362 位成员
B
创建者:约翰
);
}
```
### 带图片
```tsx
import {CircleDollar} from "@gravity-ui/icons";
import {Avatar, Button, Card, CloseButton, Link} from "@heroui/react";
export function WithImages() {
return (
{/* 第 1 行:大图商品卡 */}
成为 ACME 创作者!
这是一段占位说明文字,用于展示横向卡片布局、配图与右上角关闭按钮的排版效果。
仅剩 10 个名额
报名截止:10 月 10 日
立即申请
{/* 第 2 行 */}
{/* 左栏 */}
{/* 上方卡片 */}
支付
现已支持加密货币提现
在设置中添加钱包即可提现
前往设置
{/* 下方小卡 */}
{/* 左卡 */}
JK
Indie Hackers
148 位成员
JK
创建者:约翰
{/* 右卡 */}
AB
AI Builders
362 位成员
M
创建者:玛莎
{/* 右栏 */}
{/* 背景图 */}
{/* 标题区 */}
NEO
家用机器人
{/* 底部 */}
通知我
{/* 第 3 行 */}
{/* 左:大图卡 */}
立即购买
{/* 右:堆叠小卡 */}
{/* 1 */}
连接未来
今天 18:30
{/* 2 */}
牛油果黑客松
周三 16:30
{/* 3 */}
Sound Electro|超越艺术
周五 20:00
);
}
```
### 搭配表单
```tsx
"use client";
import {Button, Card, Form, Input, Label, Link, TextField} from "@heroui/react";
export function WithForm() {
const onSubmit = (e: React.SyntheticEvent) => {
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("表单提交成功!");
};
return (
登录
输入账号信息以访问您的账户
邮箱
密码
登录
忘记密码?
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Check, Star} from "@gravity-ui/icons";
import {Button, Card} from "@heroui/react";
const PRO_FEATURES = ["无限项目与协作者", "优先支持,24 小时响应", "高级分析与导出"] as const;
export function CustomStyles() {
return (
推荐
升级到 Pro
解锁面向成长型产品的团队工作流与洞察。
{PRO_FEATURES.map((feature) => (
{feature}
))}
立即升级
对比方案
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Card 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.card--secondary {
@apply bg-gradient-to-br from-blue-50 to-purple-50;
}
.card__title {
@apply text-xl font-bold;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Card 使用 [BEM](https://getbem.com/) 命名以实现可预测的样式([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/card.css)):
#### 基础类 \[!toc]
* `.card` - 带 padding 与边框的基础容器
* `.card__header` - 头部区域容器
* `.card__title` - 基础字号与字重的标题
* `.card__description` - 弱化描述文本
* `.card__content` - 灵活内容容器
* `.card__footer` - 行布局的页脚
#### 变体类 \[!toc]
* `.card--transparent` - 最低 prominence,透明背景(对应 `transparent` 变体)
* `.card--default` - 标准外观,surface-secondary(默认)
* `.card--secondary` - 中等 prominence,surface-tertiary(对应 `secondary` 变体)
* `.card--tertiary` - 更高 prominence,surface-tertiary(对应 `tertiary` 变体)
## API 参考
### Card
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------------------------------------------------- | ----------- | --------------------- |
| `variant` | `"transparent" \| "default" \| "secondary" \| "tertiary"` | `"default"` | 表示 prominence 级别的语义变体 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 卡片内容 |
### Card.Header
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 头部内容 |
### Card.Title
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 标题内容(渲染为 `h3`) |
### Card.Description
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 描述内容(渲染为 `p`) |
### Card.Content
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 主要内容 |
### Card.Footer
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 页脚内容 |
## 无障碍
```tsx
import { Card } from '@heroui/react';
import { cardVariants } from '@heroui/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
# Separator 分隔符
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/separator
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(layout)/separator.mdx
> 在内容区块之间进行视觉分隔。
## 用法
```tsx
import { Separator } from '@heroui/react';
```
```tsx
import {Separator} from "@heroui/react";
export function Basic() {
return (
HeroUI v3 组件
美观、快速、现代的 React UI 库。
);
}
```
## 示例
### 变体
```tsx
import {Separator} from "@heroui/react";
export function Variants() {
return (
);
}
```
### 与 Surface 组合
Separator 组件会根据不同的表面背景自动适配,以获得更好的可见性。
```tsx
import {Separator, Surface} from "@heroui/react";
export function WithSurface() {
return (
);
}
```
### 垂直方向
```tsx
import {Separator} from "@heroui/react";
export function Vertical() {
return (
);
}
```
### 带内容
```tsx
import {Separator} from "@heroui/react";
const items = [
{
iconUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/bell-small.png",
subtitle: "接收账户活动更新",
title: "设置通知",
},
{
iconUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/compass-small.png",
subtitle: "将浏览器连接到你的账户",
title: "设置浏览器扩展",
},
{
iconUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/mint-collective-small.png",
subtitle: "创建你的第一件收藏品",
title: "铸造收藏品",
},
];
export function WithContent() {
return (
{items.map((item, index) => (
{item.title}
{item.subtitle}
{index < items.length - 1 &&
}
))}
);
}
```
### 装饰性分隔线
当分隔线仅用于视觉装饰(例如列表框分区之间的分隔线)时,使用 `isDecorative`。默认渲染带有 `aria-hidden` 和呈现性语义的 `div`,同时保留视觉样式。自定义渲染函数会接收装饰性属性和 ref。
```tsx
```
### 渲染函数
```tsx
"use client";
import {Separator} from "@heroui/react";
export function RenderFunction() {
return (
HeroUI v3 组件
美观、快速、现代的 React UI 库。
} />
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Separator} from "@heroui/react";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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];
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Separator 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/separator.css)):
#### 基础类与方向类 \[!toc]
* `.separator` - 基础 Separator 样式,默认水平方向
* `.separator--horizontal` - 水平方向(全宽,高度 1px)
* `.separator--vertical` - 垂直方向(全高,宽度 1px)
#### 变体类 \[!toc]
* `.separator--default` - 默认变体,标准对比度
* `.separator--secondary` - 次要变体,中等对比度
* `.separator--tertiary` - 第三级变体,较弱对比度
## API 参考
### Separator
| Prop | 类型 | 默认值 | 描述 |
| -------------- | ----------------------------------------------------------------- | -------------- | ----------------------- |
| `isDecorative` | `boolean` | `false` | 对辅助技术隐藏纯装饰性分隔线,并移除分隔符语义 |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Separator 的方向 |
| `variant` | `'default' \| 'secondary' \| 'tertiary'` | `'default'` | Separator 的视觉变体 |
| `className` | `string` | - | 额外的 CSS 类名 |
| `render` | `DOMRenderFunction` | - | 通过自定义渲染函数覆盖默认的 DOM 元素。 |
## 相关组件
## Related Components
* **Card**: Content container with header, body, and footer
* **Chip**: Compact elements for tags and filters
* **Avatar**: Display user profile images
# Surface 表面
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/surface
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(layout)/surface.mdx
> 提供表面级样式与子组件上下文的容器组件。
## 用法
```tsx
import { Surface } from '@heroui/react';
```
```tsx
import {Surface} from "@heroui/react";
export function Basic() {
return (
表面内容
这是默认表面变体,使用 bg-surface 样式。
);
}
```
## 示例
### 变体
Surface 提供描述 prominence 层级的语义变体:
* **`default`** - 标准表面外观(bg-surface)
* **`secondary`** - 中等 prominence(bg-surface-secondary)
* **`tertiary`** - 较高 prominence(bg-surface-tertiary)
```tsx
import {Surface} from "@heroui/react";
export function Variants() {
return (
默认
表面内容
这是默认表面变体,使用 bg-surface 样式。
次要
表面内容
这是次要表面变体,使用 bg-surface-secondary 样式。
第三
表面内容
这是第三表面变体,使用 bg-surface-tertiary 样式。
透明
表面内容
这是透明表面变体,无背景,适用于遮罩层和自定义背景的卡片。
);
}
```
### 搭配表单
在 Surface 内使用表单组件时,请使用 `variant="secondary"` 以应用适合表面背景的低强调变体。
```tsx
"use client";
import {Input, Surface, TextArea} from "@heroui/react";
export function WithFormComponents() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Surface} from "@heroui/react";
export function CustomStyles() {
return (
账单概览
在此查看发票和支付方式。
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Surface 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/surface.css)):
#### 基础类 \[!toc]
* `.surface` - Surface 根容器
#### 变体类 \[!toc]
* `.surface--default` - 默认 Surface 变体(bg-surface)
* `.surface--secondary` - Secondary Surface 变体(bg-surface-secondary)
* `.surface--tertiary` - Tertiary Surface 变体(bg-surface-tertiary)
## API 参考
### Surface
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ---------------------------------------------------------- | ----------- | -------------- |
| `variant` | ` "transparent" \| "default" \| "secondary" \| "tertiary"` | `"default"` | Surface 的视觉变体。 |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode` | - | Surface 内容。 |
## Context API
### SurfaceContext
子组件可通过 Surface 上下文读取当前变体:
```tsx
import { useContext } from 'react';
import { SurfaceContext } from '@heroui/react';
function MyComponent() {
const { variant } = useContext(SurfaceContext);
// variant 为 "transparent" | "default" | "secondary" | "tertiary" | undefined
}
```
## 相关组件
## Related Components
* **CheckboxGroup**: Group of checkboxes with shared state
* **Fieldset**: Group related form controls with legends
* **InputOTP**: One-time password input
# Toolbar 工具栏
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/toolbar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(layout)/toolbar.mdx
> 用于承载可交互控件的容器,并支持方向键导航。
## 用法
```tsx
import { Toolbar } from '@heroui/react';
```
```tsx
import {Bold, Copy, Italic, Scissors, Underline} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function Basic() {
return (
);
}
```
## 示例
### 垂直方向
```tsx
import {ArrowUturnCcwLeft, ArrowUturnCwRight, Bold, Italic, Underline} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function Vertical() {
return (
);
}
```
### 组合模式
```tsx
import {Bold, Copy, Italic, Scissors, Underline} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function Attached() {
return (
);
}
```
### 与 ButtonGroup 组合
```tsx
import {
ArrowUturnCcwLeft,
ArrowUturnCwRight,
Bold,
Italic,
TextAlignCenter,
TextAlignLeft,
TextAlignRight,
Underline,
} from "@gravity-ui/icons";
import {
Button,
ButtonGroup,
Separator,
ToggleButton,
ToggleButtonGroup,
Toolbar,
} from "@heroui/react";
export function WithButtonGroup() {
return (
撤销
重做
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Bold, Italic, Underline} from "@gravity-ui/icons";
import {ToggleButton, ToggleButtonGroup, Toolbar} from "@heroui/react";
const toggleClass =
"rounded-lg data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground";
export function CustomStyles() {
return (
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.toolbar {
@apply gap-4 rounded-lg bg-surface p-3;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Toolbar 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/toolbar.css)):
* `.toolbar` - 基础容器
* `.toolbar--horizontal` - 水平方向(默认)
* `.toolbar--vertical` - 垂直方向
* `.toolbar--attached` - Attached 变体:surface 背景与完全圆角
## API 参考
### Toolbar
继承 [React Aria Toolbar](https://react-spectrum.adobe.com/react-aria/Toolbar.html)。
| Prop | 类型 | 默认值 | 描述 |
| ----------------- | -------------------------------------------------------------------- | -------------- | ----------------------------- |
| `isAttached` | `boolean` | `false` | Toolbar 是否使用带完全圆角的 surface 背景 |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Toolbar 的方向 |
| `aria-label` | `string` | - | Toolbar 的无障碍标签 |
| `aria-labelledby` | `string` | - | 用于标注该 Toolbar 的元素 id |
| `children` | `React.ReactNode \| (values: ToolbarRenderProps) => React.ReactNode` | - | 内容或渲染 prop |
| `className` | `string \| (values: ToolbarRenderProps) => string` | - | 额外的 CSS 类名 |
### ToolbarRenderProps
使用渲染 prop 模式时,会提供以下值:
| Prop | 类型 | 描述 |
| ------------- | ---------------------------- | -------------- |
| `orientation` | `"horizontal" \| "vertical"` | 当前 Toolbar 的方向 |
## 相关组件
## Related Components
* **ButtonGroup**: Group related buttons together
* **ToggleButtonGroup**: Group multiple toggle buttons into a unified control
* **Separator**: Visual divider between content
# AvatarGroup 头像组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/avatar-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(media)/avatar-group.mdx
> 以叠放或网格展示多个头像,并支持溢出计数
## 用法
```tsx
import { AvatarGroup, Avatar } from '@heroui/react';
```
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "张明",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "李华",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "王芳",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "刘洋",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "陈静",
},
];
function initialsFromName(name: string) {
const parts = name.split(/\s+/).filter(Boolean);
if (parts.length >= 2) {
return parts.map((n) => n[0]).join("");
}
return name.slice(0, 2);
}
export function Basic() {
return (
{users.slice(0, 4).map((user) => (
{initialsFromName(user.name)}
))}
);
}
```
## 组件结构
```tsx
import { AvatarGroup, Avatar } from '@heroui/react';
export default () => (
{/* 可选的显式子组件 */}
);
```
## 示例
### 最大数量
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "张明",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "李华",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "王芳",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "刘洋",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "陈静",
},
];
function initialsFromName(name: string) {
const parts = name.split(/\s+/).filter(Boolean);
if (parts.length >= 2) {
return parts.map((n) => n[0]).join("");
}
return name.slice(0, 2);
}
export function Max() {
return (
{users.map((user) => (
{initialsFromName(user.name)}
))}
);
}
```
### 自定义计数
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "张明",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "李华",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "王芳",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "刘洋",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "陈静",
},
];
function initialsFromName(name: string) {
const parts = name.split(/\s+/).filter(Boolean);
if (parts.length >= 2) {
return parts.map((n) => n[0]).join("");
}
return name.slice(0, 2);
}
export function Count() {
const total = 12;
return (
{users.slice(0, 3).map((user) => (
{initialsFromName(user.name)}
))}
+{total - 3}
);
}
```
### 尺寸
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "张明",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "李华",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "王芳",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "刘洋",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "陈静",
},
];
function initialsFromName(name: string) {
const parts = name.split(/\s+/).filter(Boolean);
if (parts.length >= 2) {
return parts.map((n) => n[0]).join("");
}
return name.slice(0, 2);
}
export function Sizes() {
return (
小号
{users.slice(0, 4).map((user) => (
{initialsFromName(user.name)}
))}
中号(默认)
{users.slice(0, 4).map((user) => (
{initialsFromName(user.name)}
))}
大号
{users.slice(0, 4).map((user) => (
{initialsFromName(user.name)}
))}
);
}
```
### 网格
```tsx
import {Avatar, AvatarGroup} from "@heroui/react";
const users = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "张明",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "李华",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "王芳",
},
{
id: 4,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
name: "刘洋",
},
{
id: 5,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
name: "陈静",
},
];
function initialsFromName(name: string) {
const parts = name.split(/\s+/).filter(Boolean);
if (parts.length >= 2) {
return parts.map((n) => n[0]).join("");
}
return name.slice(0, 2);
}
export function Grid() {
return (
{users.map((user) => (
{initialsFromName(user.name)}
))}
);
}
```
### 叠放样式(Overlap)
```tsx
import type {ReactNode} from "react";
import {Avatar, AvatarGroup} from "@heroui/react";
const STRIPE_STYLES = `
@keyframes ag-stripes {
to { background-position: 24px 24px; }
}
.ag-stripes {
background-color: color-mix(in oklab, var(--danger) 6%, var(--surface));
background-image: repeating-linear-gradient(
-45deg,
transparent 0 10px,
color-mix(in oklab, var(--danger) 12%, transparent) 10px 11px,
transparent 11px 21px,
color-mix(in oklab, var(--danger) 28%, transparent) 21px 22px
);
background-size: 24px 24px;
animation: ag-stripes 2.8s linear infinite;
}
`;
function StripeBackdrop({children}: {children: ReactNode}) {
return (
);
}
function DemoGroup({overlap}: {overlap: "clip" | "ring"}) {
return (
JD
AB
EC
SM
+2
);
}
export function Overlap() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Person} from "@gravity-ui/icons";
import {Avatar, AvatarGroup} from "@heroui/react";
const assignees = [
{
id: 1,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
name: "张明",
},
{
id: 2,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
name: "李华",
},
{
id: 3,
image: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
name: "王芳",
},
] as const;
export function CustomStyles() {
return (
{assignees.map((user) => (
{user.name.slice(0, 1)}
))}
+3
指派人
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 AvatarGroup 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.avatar-group {
--avatar-group-overlap: 0.75rem;
--avatar-group-seam: 2px;
}
.avatar-group__count {
@apply font-semibold;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
AvatarGroup 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/avatar-group.css)):
#### 基础类 \[!toc]
* `.avatar-group` - 头像组基础容器
* `.avatar-group--grid` - 网格布局修饰符(无叠放)
* `.avatar-group--clip` - 沿相邻头像轮廓裁切出的透明缝(默认叠放)
* `.avatar-group--ring` - 通过 `--background` 的实线 box-shadow 描边
* `.avatar-group__count` - 溢出计数头像
叠放布局在兄弟元素之间使用负外边距(`--avatar-group-overlap`,默认 `0.5rem`)。clip 模式对非末子头像做遮罩,并光学微调 fallback 字形;ring 模式使用 `--background` 细线 box-shadow 描边。
## API 参考
### AvatarGroup
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | -------- | ------------------------------------------------- |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 当子 Avatar 未指定 `size` 时应用到子头像(及溢出计数);默认与 Avatar 一致 |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | - | 当子 Avatar 未指定 `color` 时,应用到子头像(及溢出计数)的颜色 |
| `variant` | `'default' \| 'soft'` | - | 当子 Avatar 未指定 `variant` 时,应用到子头像(及溢出计数)的变体 |
| `max` | `number` | - | 最多渲染的头像子项数量;省略则显示全部 |
| `isGrid` | `boolean` | `false` | 是否使用无叠放的换行网格布局 |
| `overlap` | `'clip' \| 'ring'` | `'clip'` | 叠放样式:透明缝裁切(默认)或 box-shadow 描边;`isGrid` 时忽略 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `React.ReactNode` | - | 要组合的 Avatar 组件(以及可选的 `AvatarGroup.Count`) |
### AvatarGroup.Count
由 `Avatar` + `Avatar.Fallback` 组合而成,用于溢出指示。不受 `max` 截断。
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | --- | ------------- |
| `children` | `React.ReactNode` | - | 计数内容(例如 `+3`) |
| `size` | `'sm' \| 'md' \| 'lg'` | - | 覆盖组的尺寸 |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | - | 覆盖组的颜色 |
| `variant` | `'default' \| 'soft'` | - | 覆盖组的变体 |
| `className` | `string` | - | 附加 CSS 类 |
## 注意事项
* AvatarGroup 通过 React Context 仅向**直接 Avatar 子组件**传递 `size`、`color` 与 `variant`
* 设置 `max` 会截断可见头像并根据剩余子项自动计数;省略则显示全部
* 已知总量(例如来自服务端)时,渲染显式的 `AvatarGroup.Count` — 它不受 `max` 截断,并会抑制自动计数。请用 `Count` 替代已移除的 v2 `total`
* `isGrid` 会禁用叠放,改为换行网格布局
* `overlap` 默认为 `"clip"`(透明缝裁切);使用 `"ring"` 可改为实线描边;与 `isGrid` 同时使用时无效
* 默认不加 `role="group"` — 当叠放有语义(指派人、在看的人)时,自行加上 `role="group"`,并配合 `aria-label` 或 `aria-labelledby`
## 相关组件
## Related Components
* **Avatar**: Display user profile images
* **Badge**: Small indicator positioned relative to another element
* **Chip**: Compact elements for tags and filters
# Avatar 头像
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/avatar
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(media)/avatar.mdx
> 显示用户头像,支持自定义 fallback 内容
## 用法
```tsx
import { Avatar } from '@heroui/react';
```
```tsx
import {Avatar} from "@heroui/react";
export function Basic() {
return (
);
}
```
## 组件结构
```tsx
import { Avatar } from '@heroui/react';
export default () => (
)
```
## 示例
### 尺寸
```tsx
import {Avatar} from "@heroui/react";
export function Sizes() {
return (
);
}
```
### 颜色
```tsx
import {Avatar} from "@heroui/react";
export function Colors() {
return (
);
}
```
### 变体
```tsx
import {Person} from "@gravity-ui/icons";
import {Avatar, Separator} from "@heroui/react";
const colors = ["accent", "default", "success", "warning", "danger"] as const;
const COLOR_LABELS: Record<(typeof colors)[number], string> = {
accent: "强调",
danger: "危险",
default: "默认",
success: "成功",
warning: "警告",
};
const variants = [
{content: "AG", label: "字母", type: "letter"},
{content: "AG", label: "柔和字母", type: "letter-soft"},
{content: , label: "图标", type: "icon"},
{content: , label: "柔和图标", type: "icon-soft"},
{
content: [
"https://img.heroui.chat/image/avatar?w=400&h=400&u=3",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=4",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=5",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=8",
"https://img.heroui.chat/image/avatar?w=400&h=400&u=16",
],
label: "图片",
type: "img",
},
] as const;
export function Variants() {
return (
{/* 颜色列标题 */}
{colors.map((color) => (
{COLOR_LABELS[color]}
))}
{/* 变体行 */}
{variants.map((variant) => (
{variant.label}
{colors.map((color, colorIndex) => (
{variant.type === "img" ? (
<>
{COLOR_LABELS[color].charAt(0)}
>
) : (
{variant.content}
)}
))}
))}
);
}
```
### 回退内容
```tsx
import {Person} from "@gravity-ui/icons";
import {Avatar} from "@heroui/react";
export function Fallback() {
return (
{/* 文字回退 */}
JD
{/* 图标回退 */}
{/* 延迟显示回退 */}
NA
{/* 自定义样式回退 */}
GB
);
}
```
## 自定义样式
### 自定义图片组件
在 `Avatar.Image` 上使用 `asChild`,即可与自定义图片组件组合。以下示例使用 Next.js `Image` 以实现优化加载。请将 `src` 传给 `Avatar.Image`,以便跟踪加载状态,并在图片就绪前显示回退内容。
```tsx
import {Avatar} from "@heroui/react";
import Image from "next/image";
const SRC = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg";
export function CustomImageComponent() {
return (
JD
);
}
```
### Tailwind CSS
```tsx
import {Avatar} from "@heroui/react";
export function CustomStyles() {
return (
张三
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Avatar 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.avatar {
@apply size-16 border-2 border-accent;
}
.avatar__fallback {
@apply bg-gradient-to-br from-purple-500 to-pink-500;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Avatar 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/avatar.css)):
#### 基础类 \[!toc]
* `.avatar` - 基础容器,默认尺寸(size-10)
* `.avatar__image` - 图片元素,aspect-square 尺寸
* `.avatar__fallback` - 居中内容的 fallback 容器
#### 尺寸修饰符 \[!toc]
* `.avatar--sm` - 小头像(size-8)
* `.avatar--md` - 中头像(默认,无额外样式)
* `.avatar--lg` - 大头像(size-12)
#### 变体修饰符 \[!toc]
* `.avatar--soft` - 浅色背景的 soft 变体
#### 颜色修饰符 \[!toc]
* `.avatar__fallback--default` - 默认文字颜色
* `.avatar__fallback--accent` - 强调色文字
* `.avatar__fallback--success` - 成功色文字
* `.avatar__fallback--warning` - 警告色文字
* `.avatar__fallback--danger` - 危险色文字
## API 参考
### Avatar
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | ----------- | ------------- |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 头像尺寸 |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | `'default'` | Fallback 颜色主题 |
| `variant` | `'default' \| 'soft'` | `'default'` | 视觉样式变体 |
| `className` | `string` | - | 附加 CSS 类 |
### Avatar.Image
| Prop | 类型 | 默认值 | 描述 |
| ------------- | --------------------------------------------------- | ------- | ----------------------------------------- |
| `src` | `string` | - | 图片 URL |
| `srcSet` | `string` | - | 响应式图片的 `srcset` 属性 |
| `sizes` | `string` | - | 响应式图片的 `sizes` 属性 |
| `alt` | `string` | - | 图片替代文本 |
| `asChild` | `boolean` | `false` | 将属性合并到子元素上(例如 `next/image`),而不是渲染原生 `img` |
| `onLoad` | `(event: SyntheticEvent) => void` | - | 图片加载成功时的回调 |
| `onError` | `(event: SyntheticEvent) => void` | - | 图片加载失败时的回调 |
| `crossOrigin` | `'anonymous' \| 'use-credentials'` | - | 图片请求的 CORS 设置 |
| `loading` | `'eager' \| 'lazy'` | - | 原生懒加载属性 |
| `className` | `string` | - | 附加 CSS 类 |
### Avatar.Fallback
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | --- | ---------------------- |
| `delayMs` | `number` | - | 显示 fallback 前的延迟(防止闪烁) |
| `color` | `'default' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | - | 覆盖父级颜色 |
| `className` | `string` | - | 附加 CSS 类 |
## 相关组件
## Related Components
* **AvatarGroup**: Display a stacked or grid group of avatars
* **Separator**: Visual divider between content
* **Badge**: Small indicator positioned relative to another element
# Accordion 手风琴
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/accordion
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(navigation)/accordion.mdx
> 用于在紧凑空间中组织信息的可折叠内容面板
## 用法
```tsx
import { Accordion } from '@heroui/react';
```
```tsx
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
const items = [
{
content: "浏览我们的商品,将商品加入购物车并前往结账。完成购买需要提供收货与支付信息。",
icon: ,
title: "如何下单?",
},
{
content: "可以,在订单发货前你可以修改或取消。订单一旦进入处理流程,将无法再更改。",
icon: ,
title: "可以修改或取消订单吗?",
},
{
content: "我们接受主流信用卡,包括 Visa、Mastercard 和 American Express。",
icon: ,
title: "支持哪些支付方式?",
},
{
content: "运费因收货地址与订单体积而异。订单满 50 美元可享受免运费。",
icon: ,
title: "运费如何计算?",
},
{
content: "是的,我们可向多数国家/地区发货。请查看运费说明与政策了解更多信息。",
icon: ,
title: "是否提供国际配送?",
},
{
content: "若对购买不满意,可在购买后 30 天内申请退款。请联系客服团队协助处理。",
icon: ,
title: "如何申请退款?",
},
];
export function Basic() {
return (
{items.map((item, index) => (
{item.icon ? (
{item.icon}
) : null}
{item.title}
{item.content}
))}
);
}
```
## 组件结构
```tsx
import { Accordion } from '@heroui/react';
export default () => (
)
```
## 示例
### 表面样式
```tsx
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
const items = [
{
content: "浏览我们的商品,将商品加入购物车并前往结账。完成购买需要提供收货与支付信息。",
icon: ,
title: "如何下单?",
},
{
content: "可以,在订单发货前你可以修改或取消。订单一旦进入处理流程,将无法再更改。",
icon: ,
title: "可以修改或取消订单吗?",
},
{
content: "我们接受主流信用卡,包括 Visa、Mastercard 和 American Express。",
icon: ,
title: "支持哪些支付方式?",
},
{
content: "运费因收货地址与订单体积而异。订单满 50 美元可享受免运费。",
icon: ,
title: "运费如何计算?",
},
{
content: "是的,我们可向多数国家/地区发货。请查看运费说明与政策了解更多信息。",
icon: ,
title: "是否提供国际配送?",
},
{
content: "若对购买不满意,可在购买后 30 天内申请退款。请联系客服团队协助处理。",
icon: ,
title: "如何申请退款?",
},
];
export function Surface() {
return (
{items.map((item, index) => (
{item.icon ? (
{item.icon}
) : null}
{item.title}
{item.content}
))}
);
}
```
### 无分隔线
```tsx
import {ChevronDown, CreditCard, Receipt, ShoppingBag} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
const items = [
{
content: "浏览我们的商品,将商品加入购物车并前往结账。完成购买需要提供收货与支付信息。",
icon: ,
title: "如何下单?",
},
{
content: "可以,在订单发货前你可以修改或取消。订单一旦进入处理流程,将无法再更改。",
icon: ,
title: "可以修改或取消订单吗?",
},
{
content: "我们接受主流信用卡,包括 Visa、Mastercard 和 American Express。",
icon: ,
title: "支持哪些支付方式?",
},
];
export function WithoutSeparator() {
return (
{items.map((item, index) => (
{item.icon ? (
{item.icon}
) : null}
{item.title}
{item.content}
))}
);
}
```
### 多项展开
```tsx
import {Accordion} from "@heroui/react";
export function Multiple() {
return (
快速开始
了解 HeroUI 的基础知识,以及如何将其集成到你的 React
项目中。本节涵盖安装、配置和你的第一个组件。
核心概念
理解 HeroUI 背后的核心概念,包括复合组件模式、使用 Tailwind CSS
进行样式设计,以及无障碍特性。
高级用法
探索高级特性,例如自定义变体、主题定制,以及与 React 生态中其他库的集成。
最佳实践
遵循我们建议的最佳实践,使用 HeroUI 构建高性能、无障碍且易于维护的应用。
);
}
```
### 禁用状态
```tsx
import {Accordion} from "@heroui/react";
export function Disabled() {
return (
整个手风琴禁用
禁用项 1
手风琴禁用时无法查看此内容。
禁用项 2
手风琴禁用时无法查看此内容。
单独禁用条目
可用项
此项可用,可正常展开与折叠。
禁用项
条目禁用时无法查看此内容。
另一可用项
此项同样可用,可正常切换。
);
}
```
### 受控组件
```tsx
"use client";
import {ChevronDown, ChevronUp} from "@gravity-ui/icons";
import {Accordion, Button, useDisclosureGroupNavigation} from "@heroui/react";
import React from "react";
const items = [
{
content:
"了解 HeroUI 的基础知识,以及如何将其集成到你的 React 项目中。本节涵盖安装、配置和你的第一个组件。",
id: "getting-started",
title: "快速开始",
},
{
content:
"理解 HeroUI 背后的核心概念,包括复合组件模式、使用 Tailwind CSS 进行样式设计,以及无障碍特性。",
id: "core-concepts",
title: "核心概念",
},
{
content: "探索高级特性,例如自定义变体、主题定制,以及与 React 生态中其他库的集成。",
id: "advanced-usage",
title: "高级用法",
},
];
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 (
已展开:{[...expandedKeys].join("、") || "无"}
{items.map((item) => (
{item.title}
{item.content}
))}
);
}
```
### 自定义指示器
```tsx
"use client";
import type {Key} from "@heroui/react";
import {ChevronsDown, CircleChevronDown, Minus, Plus} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
import React from "react";
export function CustomIndicator() {
const [expandedKeys, setExpandedKeys] = React.useState>(new Set([""]));
return (
使用加号/减号图标
{expandedKeys.has("1") ? : }
折叠时显示加号图标,展开时切换为减号图标。
使用圆形箭头图标
此项使用圆形内的箭头作为指示器,旋转动画会自动应用。
使用双箭头图标
此项使用双箭头图标。传入任意图标后,在条目展开时都会获得旋转动画。
);
}
```
### 渲染函数
```tsx
"use client";
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag,
} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
const items = [
{
content: "浏览我们的商品,将商品加入购物车并前往结账。完成购买需要提供收货与支付信息。",
icon: ,
title: "如何下单?",
},
{
content: "可以,在订单发货前你可以修改或取消。订单一旦进入处理流程,将无法再更改。",
icon: ,
title: "可以修改或取消订单吗?",
},
{
content: "我们接受主流信用卡,包括 Visa、Mastercard 和 American Express。",
icon: ,
title: "支持哪些支付方式?",
},
{
content: "运费因收货地址与订单体积而异。订单满 50 美元可享受免运费。",
icon: ,
title: "运费如何计算?",
},
{
content: "是的,我们可向多数国家/地区发货。请查看运费说明与政策了解更多信息。",
icon: ,
title: "是否提供国际配送?",
},
{
content: "若对购买不满意,可在购买后 30 天内申请退款。请联系客服团队协助处理。",
icon: ,
title: "如何申请退款?",
},
];
export function RenderFunction() {
return (
}
>
{items.map((item, index) => (
}>
}>
}>
{item.icon ? (
{item.icon}
) : null}
{item.title}
}>
{item.content}
))}
);
}
```
### 常见问题布局
```tsx
import {ChevronDown} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
export function FAQ() {
const categories = [
{
items: [
{
content: "浏览我们的商品,将商品加入购物车并前往结账。完成购买需要提供收货与支付信息。",
title: "如何下单?",
},
{
content: "可以,在订单发货前你可以修改或取消。订单一旦进入处理流程,将无法再更改。",
title: "可以修改或取消订单吗?",
},
],
title: "常规",
},
{
items: [
{
content: "你可以直接在官网购买许可证,选择适合的许可证类型后前往结账即可。",
title: "如何购买许可证?",
},
{
content: "标准版适用于个人或小项目;专业版包含商业使用授权与优先支持。",
title: "标准版与专业版有什么区别?",
},
],
title: "许可",
},
{
items: [
{
content: "可通过网站上的联系表单联系支持团队,或直接发送邮件至 support@example.com。",
title: "如何获取支持?",
},
],
title: "支持",
},
];
return (
常见问题
关于许可与使用,你需要了解的内容都在这里。
{categories.map((category) => (
{category.title}
{category.items.map((item, index) => (
{item.title}
{item.content}
))}
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ChevronDown} from "@gravity-ui/icons";
import {Accordion} from "@heroui/react";
const items = [
{
content: "通过实时通知及时了解账户动态。",
iconUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/bell-small.png",
subtitle: "接收账户活动更新",
title: "开启通知",
},
{
content: "安装我们的官方浏览器扩展,获得更顺畅的浏览体验。",
iconUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/compass-small.png",
subtitle: "将浏览器连接到你的账户",
title: "安装浏览器扩展",
},
{
content: "创建你的第一件数字藏品,开启数字收藏之旅。",
iconUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/mint-collective-small.png",
subtitle: "创建你的第一件收藏品",
title: "铸造收藏品",
},
];
export function CustomStyles() {
return (
{items.map((item, index) => (
{item.iconUrl ? (
) : null}
{item.title}
{item.subtitle}
{item.content}
))}
);
}
```
### 全局 CSS
若要自定义 Accordion 组件类,可使用 `@layer components` 指令。
[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Accordion 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/accordion.css)):
#### 基础类 \[!toc]
* `.accordion` - Accordion 根容器
* `.accordion__body` - 正文容器
* `.accordion__heading` - 标题包裹层
* `.accordion__indicator` - 展开/收起指示图标
* `.accordion__item` - 单个 Accordion 项
* `.accordion__panel` - 可折叠面板容器
* `.accordion__trigger` - 可点击的触发按钮
#### 变体类 \[!toc]
* `.accordion--outline` - 描边变体(边框与背景)
#### 状态类 \[!toc]
* `.accordion__trigger[aria-expanded="true"]` - 展开状态
* `.accordion__panel[aria-hidden="false"]` - 面板可见状态
### 交互状态
该组件同时支持 CSS 伪类与 data 属性:
* **悬停**:触发器上 `:hover` 或 `[data-hovered="true"]`
* **聚焦**:触发器上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **禁用**:触发器上 `:disabled` 或 `[aria-disabled="true"]`
* **展开**:触发器上 `[aria-expanded="true"]`
## API 参考
### Accordion
| Prop | 类型 | 默认值 | 描述 |
| ------------------------ | ---------------------------------------------------------------------------- | ----------- | ---------------------- |
| `allowsMultipleExpanded` | `boolean` | `false` | 是否允许多项同时展开 |
| `defaultExpandedKeys` | `Iterable` | - | 初始展开的 key |
| `expandedKeys` | `Iterable` | - | 受控的展开 key |
| `onExpandedChange` | `(keys: Set) => void` | - | 展开 key 变化时调用的事件处理函数 |
| `isDisabled` | `boolean` | `false` | 是否禁用整个 Accordion |
| `variant` | `"default" \| "surface"` | `"default"` | Accordion 的视觉变体 |
| `hideSeparator` | `boolean` | `false` | 是否隐藏 Accordion 项之间的分隔线 |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | Accordion 项 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Accordion.Item
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | -------------------------------------------------------------------------------- | ------- | --------------------- |
| `id` | `Key` | - | 该项的唯一标识 |
| `isDisabled` | `boolean` | `false` | 是否禁用该项 |
| `defaultExpanded` | `boolean` | `false` | 初始是否展开 |
| `isExpanded` | `boolean` | - | 受控展开状态 |
| `onExpandedChange` | `(isExpanded: boolean) => void` | - | 展开状态变化时调用的事件处理函数 |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 项内容 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Accordion.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ------------ | -------------------------------------------------------------------------- | --- | --------------------- |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | 触发器内容或渲染函数 |
| `onPress` | `() => void` | - | 额外的按下事件处理函数 |
| `isDisabled` | `boolean` | - | 是否禁用触发器 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Accordion.Panel
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------------------------------------------------------------------------- | --- | --------------------- |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 面板内容 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Accordion.Indicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------- |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 自定义指示图标 |
### Accordion.Body
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------- |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 正文内容 |
## 相关组件
## Related Components
* **DisclosureGroup**: Group of collapsible panels
* **Disclosure**: Single collapsible content section
# Breadcrumbs 面包屑
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/breadcrumbs
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(navigation)/breadcrumbs.mdx
> 导航面包屑,显示当前页面在层级结构中的位置
## 用法
```tsx
import { Breadcrumbs } from '@heroui/react';
```
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsBasic() {
return (
首页
产品
电子产品
笔记本电脑
);
}
```
## 组件结构
```tsx
import { Breadcrumbs } from '@heroui/react';
export default () => (
Home
Category
Current Page
)
```
## 示例
### 导航层级
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsLevel2() {
return (
首页
当前页面
);
}
```
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsLevel3() {
return (
首页
分类
当前页面
);
}
```
### 禁用状态
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsDisabled() {
return (
首页
产品
电子产品
笔记本电脑
);
}
```
### 自定义分隔符
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export default function BreadcrumbsCustomSeparator() {
return (
}
>
首页
产品
电子产品
笔记本电脑
);
}
```
### 渲染函数
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export function RenderFunction() {
return (
}>
}>
首页
}>
产品
}>
电子产品
}>
笔记本电脑
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Breadcrumbs} from "@heroui/react";
export function CustomStyles() {
return (
首页
产品
笔记本电脑
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Breadcrumbs 组件类。
[了解更多](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;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Breadcrumbs 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/breadcrumbs.css)):
#### 基础类 \[!toc]
* `.breadcrumbs` - 面包屑根容器
* `.breadcrumbs__item` - 单个面包屑项包装器
* `.breadcrumbs__link` - 面包屑链接元素
* `.breadcrumbs__separator` - 项之间的分隔符图标
#### 状态类 \[!toc]
* `.breadcrumbs__link[data-current="true"]` - 当前页指示(非链接)
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Current**:链接上的 `[data-current="true"]`(表示当前页)
* **Hover**:链接元素支持标准 hover 状态
* **Disabled**:`isDisabled` 属性禁用所有链接
## API 参考
### Breadcrumbs
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ----------------------------------------------------------------- | ------------------ | -------------------------- |
| `separator` | `ReactNode` | chevron-right icon | 面包屑项之间的自定义分隔符 |
| `isDisabled` | `boolean` | `false` | 是否禁用所有面包屑链接 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | 面包屑项 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Breadcrumbs.Item
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------------------------------------------------------- | --- | -------------------------- |
| `href` | `string` | - | 链接 URL(当前页可省略) |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | 项内容或 render 函数 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
## 无障碍
Breadcrumbs 使用 React Aria Components 的 Breadcrumbs 原语,提供:
* 导航地标的正确 ARIA 属性
* 通过 `aria-current="page"` 指示当前页
* 键盘导航支持
* 导航上下文的屏幕阅读器播报
最后一项(无 `href`)自动成为当前页指示。
## 相关组件
## Related Components
* **Link**: Styled anchor links
* **Separator**: Visual divider between content
* **Tabs**: Organize content into tabbed views
# DisclosureGroup 折叠面板组
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/disclosure-group
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(navigation)/disclosure-group.mdx
> 管理多个 Disclosure 项展开状态的容器
## 用法
```tsx
import { DisclosureGroup } from '@heroui/react';
```
```tsx
"use client";
import {QrCode} from "@gravity-ui/icons";
import {Button, Disclosure, DisclosureGroup, Separator} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
import {cn} from "tailwind-variants";
export function Basic() {
const [expandedKeys, setExpandedKeys] = React.useState(new Set(["preview"]));
return (
预览 HeroUI Native
使用手机相机扫描此二维码,即可预览 HeroUI Native 组件。
设备需已安装 Expo。
在
Expo Go 预览
下载应用
下载 HeroUI Native 应用,即可在设备上直接体验我们的移动端组件。
支持 iOS 和 Android 设备。
在 App Store 下载
);
}
```
## 组件结构
```tsx
import {DisclosureGroup, Disclosure} from '@heroui/react';
export default () => (
)
```
## 示例
### 受控组件
可使用 `expandedKeys` 与 `onExpandedChange` props 配合外部导航控件控制哪些 Disclosure 处于展开状态。
```tsx
"use client";
import {ChevronDown, ChevronUp, QrCode} from "@gravity-ui/icons";
import {
Button,
Disclosure,
DisclosureGroup,
Separator,
useDisclosureGroupNavigation,
} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
import {cn} from "tailwind-variants";
export function Controlled() {
const [expandedKeys, setExpandedKeys] = React.useState(new Set(["preview"]));
const itemIds = ["preview", "download"]; // Track our disclosure items
const {isNextDisabled, isPrevDisabled, onNext, onPrevious} = useDisclosureGroupNavigation({
expandedKeys,
itemIds,
onExpandedChange: setExpandedKeys,
});
return (
预览 HeroUI Native
使用手机相机扫描此二维码,即可预览 HeroUI Native 组件。
设备需已安装 Expo。
在 Expo
Go 预览
下载 HeroUI Native
使用手机相机扫描此二维码,即可预览 HeroUI Native 组件。
设备需已安装 Expo。
在 App Store 下载
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Button, Disclosure, DisclosureGroup, Separator} from "@heroui/react";
export function CustomStyles() {
return (
账单
发票将于每月 1 日开具。
支持
请通过 help@heroui.com 联系我们。通常会在一个工作日内回复。
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 DisclosureGroup 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.disclosure-group {
@apply w-full;
/* Performance optimization */
contain: layout style;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
DisclosureGroup 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/disclosure-group.css)):
#### 基础类 \[!toc]
* `.disclosure-group` - 基础容器样式,含布局 containment
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Disabled**:整个分组上 `:disabled` 或 `[aria-disabled="true"]`
* **Expanded Management**:自动管理子 Disclosure 项上的 `[data-expanded]` 状态
## API 参考
### DisclosureGroup
| Prop | 类型 | 默认值 | 描述 |
| ------------------------ | ----------------------------- | ------- | ------------------- |
| `expandedKeys` | `Set` | - | 当前展开项(受控) |
| `defaultExpandedKeys` | `Iterable` | - | 初始展开项(非受控) |
| `onExpandedChange` | `(keys: Set) => void` | - | 展开项变化时的回调 |
| `allowsMultipleExpanded` | `boolean` | `false` | 是否允许多项同时展开 |
| `isDisabled` | `boolean` | `false` | 是否禁用组内所有 Disclosure |
| `children` | `ReactNode \| RenderFunction` | - | 要渲染的 Disclosure 项 |
| `className` | `string` | - | 附加 CSS 类 |
### Render Props
使用 render prop 模式时,提供以下值:
| Prop | 类型 | 描述 |
| -------------- | ---------- | ---------- |
| `expandedKeys` | `Set` | 当前展开项的 key |
| `isDisabled` | `boolean` | 分组是否禁用 |
## 相关案例
## 相关组件
## Related Components
* **Accordion**: Collapsible content sections
* **Disclosure**: Single collapsible content section
* **Button**: Allows a user to perform an action
# Disclosure 折叠面板
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/disclosure
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(navigation)/disclosure.mdx
> 可折叠区块,包含标题、触发按钮与内容面板
## 用法
```tsx
import { Disclosure } from '@heroui/react';
```
```tsx
"use client";
import {QrCode} from "@gravity-ui/icons";
import {Button, Disclosure} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function Basic() {
const [isExpanded, setIsExpanded] = React.useState(true);
return (
预览 HeroUI Native
使用手机相机扫描此二维码,即可预览 HeroUI Native 组件。
设备需已安装 Expo。
在 App Store 下载
);
}
```
## 组件结构
```tsx
import { Disclosure } from '@heroui/react';
export default () => (
)
```
## 示例
### 渲染函数
```tsx
"use client";
import {QrCode} from "@gravity-ui/icons";
import {Button, Disclosure} from "@heroui/react";
import {Icon} from "@iconify/react";
import React from "react";
export function RenderFunction() {
const [isExpanded, setIsExpanded] = React.useState(true);
return (
}
onExpandedChange={setIsExpanded}
>
预览 HeroUI Native
}>
使用手机相机扫描此二维码,即可预览 HeroUI Native 组件。
设备需已安装 Expo。
在 App Store 下载
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {ShoppingBag} from "@gravity-ui/icons";
import {Button, Disclosure} from "@heroui/react";
import {useState} from "react";
export function CustomStyles() {
const [isExpanded, setIsExpanded] = useState(false);
return (
配送详情
订单将在 2 个工作日内发货。标准配送需 3–5 天;结账时可选择加急配送。
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Disclosure 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.disclosure {
@apply relative;
}
.disclosure__trigger {
@apply cursor-pointer;
}
.disclosure__indicator {
@apply transition-transform duration-300;
}
.disclosure__content {
@apply overflow-hidden transition-all;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Disclosure 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/disclosure.css)):
#### 基础类 \[!toc]
* `.disclosure` - 基础容器样式
* `.disclosure__heading` - 标题包裹层
* `.disclosure__trigger` - 触发按钮样式
* `.disclosure__indicator` - 箭头指示器样式
* `.disclosure__content` - 带动画的内容容器
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Expanded**:指示器上 `[data-expanded="true"]` 用于旋转
* **Focus**:触发器上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:触发器上 `:disabled` 或 `[aria-disabled="true"]`
* **Hidden**:内容上 `[aria-hidden="false"]` 控制可见性
## API 参考
### Disclosure
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | ----------------------------------------------------------------------------- | ------- | -------------------------- |
| `isExpanded` | `boolean` | `false` | 控制展开状态 |
| `onExpandedChange` | `(isExpanded: boolean) => void` | - | 展开状态变化时的回调 |
| `isDisabled` | `boolean` | `false` | 是否禁用 |
| `children` | `ReactNode \| RenderFunction` | - | 要渲染的内容 |
| `className` | `string` | - | 附加 CSS 类 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### DisclosureTrigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | --- | -------- |
| `children` | `ReactNode \| RenderFunction` | - | 触发器内容 |
| `className` | `string` | - | 附加 CSS 类 |
### DisclosureContent
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------------ | --- | -------------------------- |
| `children` | `ReactNode` | - | 显示/隐藏的内容 |
| `className` | `string` | - | 附加 CSS 类 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Render Props
使用 render prop 模式时,提供以下值:
| Prop | 类型 | 描述 |
| ------------ | --------- | ------ |
| `isExpanded` | `boolean` | 当前展开状态 |
| `isDisabled` | `boolean` | 是否禁用 |
## 相关案例
## 相关组件
## Related Components
* **Accordion**: Collapsible content sections
* **DisclosureGroup**: Group of collapsible panels
* **Button**: Allows a user to perform an action
# Link 链接
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/link
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(navigation)/link.mdx
> 用于导航的样式化锚点组件,内置图标支持。
## 用法
```tsx
import { Link } from '@heroui/react';
```
```tsx
import {Link} from "@heroui/react";
export function LinkBasic() {
return (
立即行动
);
}
```
## 组件结构
导入 Link 组件后,可通过点语法访问所有子部分。
```tsx
import { Link } from '@heroui/react';
export default () => (
Call to action
);
```
## 示例
### 图标位置
```tsx
import {Link} from "@heroui/react";
export function LinkIconPlacement() {
return (
图标在末尾(默认)
图标在开头
);
}
```
### 配合 Tailwind CSS 的文本装饰
Link 默认在悬浮时显示下划线。可使用 Tailwind CSS 的 text-decoration 工具类让下划线始终可见、完全移除,或自定义其颜色、样式、粗细与偏移。
```tsx
import {Link} from "@heroui/react";
export function LinkUnderlineAndOffset() {
return (
调整下划线偏移
偏移 1(1px 间距)
偏移 2(2px 间距)
偏移 3(3px 间距)
偏移 4(4px 间距)
);
}
```
**文本装饰线:**
* `underline` — 始终显示下划线
* `no-underline` — 移除下划线
* 默认 `Link` 样式 — 下划线在悬浮时显示
**文本装饰色:**
* `decoration-accent`、`decoration-muted` 等 — 使用主题色设置下划线颜色
* `decoration-muted/50` — 使用透明度修饰符实现半透明下划线
**文本装饰样式:**
* `decoration-solid` — 实线(默认)
* `decoration-double` — 双线
* `decoration-dotted` — 点线
* `decoration-dashed` — 虚线
* `decoration-wavy` — 波浪线
**文本装饰粗细:**
* `decoration-1`、`decoration-2`、`decoration-4` 等 — 控制下划线粗细
**下划线偏移:**
* `underline-offset-1`、`underline-offset-2`、`underline-offset-4` 等 — 调整文本与下划线间距
更多说明见 Tailwind CSS 文档:
* [text-decoration-line](https://tailwindcss.com/docs/text-decoration-line)
* [text-decoration-color](https://tailwindcss.com/docs/text-decoration-color)
* [text-decoration-style](https://tailwindcss.com/docs/text-decoration-style)
* [text-decoration-thickness](https://tailwindcss.com/docs/text-decoration-thickness)
* [text-underline-offset](https://tailwindcss.com/docs/text-underline-offset)
可用的 BEM 类:
* 基础:`link`
* 图标:`link__icon`
### 自定义图标
```tsx
import {ArrowUpRightFromSquare, Link as LinkIcon} from "@gravity-ui/icons";
import {Link} from "@heroui/react";
export function LinkCustomIcon() {
return (
);
}
```
### 渲染函数
```tsx
"use client";
import {Link} from "@heroui/react";
export function RenderFunction() {
return (
}>
立即行动
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Link} from "@heroui/react";
export function CustomStyles() {
return (
立即行动
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.link {
@apply font-semibold no-underline hover:underline;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Link 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/link.css)):
#### 基础类 \[!toc]
* `.link` — 链接基础样式
* `.link__icon` — 链接图标样式
### 交互状态
组件同时支持 CSS 伪类与 data 属性,以获得更大灵活性:
* **焦点**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **悬浮**:`:hover` 或 `[data-hovered="true"]`
* **按下**:`:active` 或 `[data-pressed="true"]`
* **禁用**:`:disabled` 或 `[aria-disabled="true"]`
## API 参考
### Link
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ----------------------------------------------------------------------- | --------- | --------------------- |
| `href` | `string` | - | 锚点的目标 URL |
| `target` | `string` | `"_self"` | 在何处打开链接文档 |
| `rel` | `string` | - | 当前文档与链接文档的关系 |
| `download` | `boolean \| string` | - | 触发下载而非导航 |
| `isDisabled` | `boolean` | `false` | 禁用指针与键盘交互 |
| `className` | `string` | - | 与默认样式合并的自定义类 |
| `children` | `React.ReactNode` | - | 渲染在链接内部的内容 |
| `onPress` | `(e: PressEvent) => void` | - | 链接被激活时触发 |
| `autoFocus` | `boolean` | - | 元素挂载时是否应获得焦点 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Link.Icon
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ------------------- |
| `children` | `React.ReactNode` | - | 自定义图标元素;省略时使用内置箭头图标 |
| `className` | `string` | - | 附加的 CSS 类 |
### 与路由库配合使用
使用变体函数为框架专用链接(例如 Next.js)添加样式:
```tsx
import { Link } from '@heroui/react';
import { linkVariants } from '@heroui/styles';
import NextLink from 'next/link';
export default function Demo() {
const slots = linkVariants();
return (
About Page
);
}
```
### 直接应用类
由于 HeroUI 使用 [BEM](https://getbem.com/) 类,你可以将 Link 样式直接应用到任意链接元素:
```tsx
import NextLink from 'next/link';
// 直接使用 Tailwind 工具类
export default function Demo() {
return (
About Page
);
}
// 或使用原生
export default function NativeLink() {
return (
About Page
);
}
```
## 相关组件
## Related Components
* **Breadcrumbs**: Display the user's current location within a hierarchy
# Pagination 分页
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/pagination
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(navigation)/pagination.mdx
> 分页导航:可组合的页码链接、上一页/下一页按钮与省略号指示器。
## 用法
```tsx
import { Pagination } from '@heroui/react';
```
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationBasic() {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
setPage((p) => p - 1)}>
上一页
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
下一页
);
}
```
## 组件结构
导入 Pagination 组件后,可通过点号访问各个子部分。
```tsx
import { Pagination } from '@heroui/react';
export default () => (
Showing 1-10 of 100 results
Previous
1
10
Next
);
```
## 示例
### 尺寸
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
const SIZE_LABELS = {
lg: "大",
md: "中",
sm: "小",
} as const;
function SizePagination({size}: {size: "sm" | "md" | "lg"}) {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
{SIZE_LABELS[size]}
setPage((p) => p - 1)}>
上一页
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
下一页
);
}
export function PaginationSizes() {
return (
{(["sm", "md", "lg"] as const).map((size) => (
))}
);
}
```
### 禁用
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationDisabled() {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
setPage((p) => p - 1)}>
上一页
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
下一页
);
}
```
### 简化(上一页 / 下一页)
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationSimplePrevNext() {
const [page, setPage] = useState(1);
const totalPages = 10;
const itemsPerPage = 5;
const totalItems = 50;
const startItem = (page - 1) * itemsPerPage + 1;
const endItem = Math.min(page * itemsPerPage, totalItems);
return (
第 {startItem}–{endItem} 条,共 {totalItems} 张发票
setPage((p) => p - 1)}>
上一页
setPage((p) => p + 1)}>
下一页
);
}
```
### 受控组件
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationControlled() {
const [page, setPage] = useState(1);
const totalPages = 12;
const itemsPerPage = 10;
const totalItems = 120;
const getPageNumbers = () => {
const pages: (number | "ellipsis")[] = [];
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
pages.push(1);
if (page > 3) {
pages.push("ellipsis");
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPages - 1, page + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (page < totalPages - 2) {
pages.push("ellipsis");
}
pages.push(totalPages);
}
return pages;
};
const startItem = (page - 1) * itemsPerPage + 1;
const endItem = Math.min(page * itemsPerPage, totalItems);
return (
显示第 {startItem}–{endItem} 条,共 {totalItems} 条结果
setPage((p) => p - 1)}>
上一页
{getPageNumbers().map((p, i) =>
p === "ellipsis" ? (
) : (
setPage(p)}>
{p}
),
)}
setPage((p) => p + 1)}>
下一页
);
}
```
### 带省略号
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationWithEllipsis() {
const [page, setPage] = useState(1);
const totalPages = 12;
const getPageNumbers = () => {
const pages: (number | "ellipsis")[] = [];
pages.push(1);
if (page > 3) {
pages.push("ellipsis");
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPages - 1, page + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (page < totalPages - 2) {
pages.push("ellipsis");
}
pages.push(totalPages);
return pages;
};
return (
setPage((p) => p - 1)}>
上一页
{getPageNumbers().map((p, i) =>
p === "ellipsis" ? (
) : (
setPage(p)}>
{p}
),
)}
setPage((p) => p + 1)}>
下一页
);
}
```
### 带摘要
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
export function PaginationWithSummary() {
const [page, setPage] = useState(1);
const totalPages = 12;
const itemsPerPage = 10;
const totalItems = 120;
const getPageNumbers = () => {
const pages: (number | "ellipsis")[] = [];
pages.push(1);
if (page > 3) {
pages.push("ellipsis");
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPages - 1, page + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (page < totalPages - 2) {
pages.push("ellipsis");
}
pages.push(totalPages);
return pages;
};
const startItem = (page - 1) * itemsPerPage + 1;
const endItem = Math.min(page * itemsPerPage, totalItems);
return (
显示第 {startItem}–{endItem} 条,共 {totalItems} 条结果
setPage((p) => p - 1)}>
上一页
{getPageNumbers().map((p, i) =>
p === "ellipsis" ? (
) : (
setPage(p)}>
{p}
),
)}
setPage((p) => p + 1)}>
下一页
);
}
```
### 自定义图标
可通过向 `PreviousIcon` 与 `NextIcon` 传入自定义子元素来替换默认 chevron 图标。
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function PaginationCustomIcons() {
const [page, setPage] = useState(1);
const totalPages = 3;
return (
setPage((p) => p - 1)}>
返回
{Array.from({length: totalPages}, (_, i) => i + 1).map((p) => (
setPage(p)}>
{p}
))}
setPage((p) => p + 1)}>
前进
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {Pagination} from "@heroui/react";
import {useState} from "react";
const linkClass = "text-muted hover:bg-surface hover:text-foreground";
const activeClass = "bg-accent text-accent-foreground hover:bg-accent-hover";
export function CustomStyles() {
const [page, setPage] = useState(2);
return (
setPage((p) => p - 1)}
>
{[1, 2, 3].map((p) => (
setPage(p)}
>
{p}
))}
setPage((p) => p + 1)}
>
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.pagination {
@apply gap-8;
}
.pagination__link {
@apply rounded-md;
}
.pagination__summary {
@apply text-xs font-semibold;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Pagination 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/pagination.css)):
#### 基础与布局类 \[!toc]
* `.pagination` - 根导航容器(flex 布局)
* `.pagination__summary` - 左侧信息文本容器
* `.pagination__content` - 分页项容器
* `.pagination__item` - 单个分页项包裹层
* `.pagination__link` - 页码按钮(ghost 按钮样式)
* `.pagination__link--nav` - 导航按钮修饰符(Previous/Next)
* `.pagination__ellipsis` - 省略号指示器
#### 尺寸类 \[!toc]
* `.pagination--sm` - 小尺寸变体
* `.pagination--md` - 中尺寸变体(默认)
* `.pagination--lg` - 大尺寸变体
### 交互状态
组件同时支持 CSS 伪类与 data 属性,便于灵活定制:
* **Active page**:`[data-active="true"]` 或 `[aria-current="page"]`
* **Hover**:`:hover` 或 `[data-hovered="true"]`
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:`:disabled` 或 `[aria-disabled="true"]`
* **Pressed**:`:active` 或 `[data-pressed="true"]`
## API 参考
### Pagination
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ---------------------- | ------ | ----------------------- |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 分页控件的尺寸 |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 分页部件(Summary、Content 等) |
### Pagination.Summary
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------------------ |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 摘要内容(例如 "Showing 1-10 of 120") |
### Pagination.Content
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------- |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 分页项 |
### Pagination.Item
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ---------------------------------- |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 项内容(Link、Previous、Next 或 Ellipsis) |
### Pagination.Link
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ------------------------- | ------- | ----------------------- |
| `isActive` | `boolean` | `false` | 是否为当前页 |
| `isDisabled` | `boolean` | `false` | 是否禁用链接 |
| `onPress` | `(e: PressEvent) => void` | - | 按下事件处理函数(来自 React Aria) |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 页码内容 |
### Pagination.Previous / Pagination.Next
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ------------------------- | ------- | --------------------------------- |
| `isDisabled` | `boolean` | `false` | 是否禁用按钮 |
| `onPress` | `(e: PressEvent) => void` | - | 按下事件处理函数(来自 React Aria) |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | - | 按钮内容(可与 PreviousIcon/NextIcon 组合) |
### Pagination.PreviousIcon / Pagination.NextIcon
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | ------------------- | --------------------- |
| `className` | `string` | - | 额外的 CSS 类 |
| `children` | `ReactNode` | Default chevron SVG | 用于替换默认 chevron 的自定义图标 |
### Pagination.Ellipsis
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | --------- |
| `className` | `string` | - | 额外的 CSS 类 |
## 说明
Pagination 按钮使用 `onPress` 而非 `onClick`。React Aria 的 `onPress` 处理函数会统一不同指针类型的按压行为,并内置无障碍改进。
## 无障碍
Pagination 基于 [React Aria 的 Button](https://react-spectrum.adobe.com/react-aria/Button.html) 原语实现所有可交互元素,并提供:
* 语义化 `` 元素,包含 `aria-label="pagination"` 与 `role="navigation"`
* 通过在当前链接上使用 `aria-current="page"` 标示活动页
* 通过 Tab 键在全部可交互元素间进行键盘导航
* 通过 React Aria 在鼠标、触摸与键盘交互之间统一处理按下事件
* 键盘导航时通过 `:focus-visible` 显示焦点环
* 省略号使用 `aria-hidden="true"`,避免干扰屏幕阅读器
* 通过 `isDisabled` 向辅助技术正确传达禁用状态
> **说明:** Pagination 按钮请使用 `onPress` 而不是 `onClick`。React Aria 的 `onPress` 会规范化不同指针类型的按下行为,并提供开箱即用的无障碍改进。
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Link**: Styled anchor links
# Tabs 标签页
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/tabs
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(navigation)/tabs.mdx
> Tabs 将内容组织为多个区块,并允许用户在它们之间导航。
## 用法
```tsx
import { Tabs } from '@heroui/react';
```
## 组件结构
导入 Tabs 组件后,可通过点语法访问所有子部分。
```tsx
import { Tabs } from '@heroui/react';
export default () => (
{/* Optional */}
)
```
## 示例
### 垂直布局
### 溢出
当标签列表超出可用空间时,`Tabs.ListContainer` 会自动渲染滚动箭头与渐隐边缘,使用户可以导航到被隐藏的标签。水平与垂直方向均支持此行为。
### 禁用 Tab
### 带分隔线
在每个 `` 内(第一项除外)添加 ` `,用于在标签之间显示分隔线。
### 次要类型
### 次要类型(垂直)
### 对齐方式
标签内容默认居中。使用 `align` 可将其对齐到 `start` 或 `end`,这在垂直的侧边栏式导航中尤其有用。单个标签仍可通过 `Tabs.Tab` 上的 `className` 覆盖。
### 渲染函数
```tsx
"use client";
import {Tabs} from "@heroui/react";
import Link from "next/link";
export function RenderFunction() {
return (
}>
}
>
快速入门
}
>
组件
}
>
发布说明
查看项目概览与近期活动。
跟踪指标并分析性能数据。
生成并下载详细报告。
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Tabs} from "@heroui/react";
const listClassName = [
"rounded-xl border border-accent/10 bg-accent-soft/30 p-1",
"**:data-[slot=tabs-tab]:rounded-lg",
"**:data-[slot=tabs-tab]:bg-transparent",
"**:data-[slot=tabs-tab]:text-muted",
"**:data-[slot=tabs-tab]:opacity-100",
"**:data-[slot=tabs-tab]:transition-colors",
"**:data-[slot=tabs-tab]:data-[hovered=true]:not-data-[selected=true]:bg-accent-soft",
"**:data-[slot=tabs-tab]:data-[hovered=true]:not-data-[selected=true]:text-accent-soft-foreground",
"**:data-[slot=tabs-tab]:data-[pressed=true]:not-data-[selected=true]:bg-accent-soft-hover",
"**:data-[slot=tabs-tab]:data-[focus-visible=true]:ring-2",
"**:data-[slot=tabs-tab]:data-[focus-visible=true]:ring-accent/15",
"**:data-[slot=tabs-tab]:data-[selected=true]:font-medium",
"**:data-[slot=tabs-tab]:data-[selected=true]:text-accent-foreground",
"**:data-[slot=tabs-tab]:shadow-none",
"**:data-[slot=tabs-indicator]:rounded-lg",
"**:data-[slot=tabs-indicator]:bg-accent",
"**:data-[slot=tabs-indicator]:shadow-none",
].join(" ");
export function CustomStyles() {
return (
按月
按年
按月计费,随时可取消。
年付可节省 20%。
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.tabs__list {
@apply rounded-xl border border-border bg-surface p-1;
}
.tabs__tab {
@apply rounded-lg font-medium;
}
.tabs__indicator {
@apply rounded-lg bg-foreground shadow-sm;
}
.tabs--secondary > .tabs__list-container > .tabs__list {
@apply border-b border-border bg-transparent p-0;
}
.tabs--secondary > .tabs__list-container .tabs__indicator {
@apply rounded-none bg-accent;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Tabs 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tabs.css)):
#### 基础类 \[!toc]
* `.tabs` - Tabs 根容器
* `.tabs__list-container` - 标签列表容器外层包裹
* `.tabs__list-container__scroller` - 标签列表的可滚动容器(处理溢出与渐隐边缘)
* `.tabs__list-container__scroll-prev` - 向前(左 / 上)滚动的箭头按钮
* `.tabs__list-container__scroll-next` - 向后(右 / 下)滚动的箭头按钮
* `.tabs__list` - 标签列表容器
* `.tabs__tab` - 单个标签按钮
* `.tabs__separator` - 标签之间的分隔线
* `.tabs__panel` - 标签面板内容
* `.tabs__indicator` - 标签指示器
#### 方向属性 \[!toc]
* `.tabs[data-orientation="horizontal"]` - 水平标签布局(默认)
* `.tabs[data-orientation="vertical"]` - 垂直标签布局
#### 变体类 \[!toc]
* `.tabs--secondary` - 次要类型变体,使用下划线指示器
#### 对齐类 \[!toc]
* `.tabs--align-start` - 标签内容对齐到起始侧
* `.tabs--align-end` - 标签内容对齐到结束侧
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Selected**:`[aria-selected="true"]`
* **Hover**:`:hover` 或 `[data-hovered="true"]`
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:`[aria-disabled="true"]`
## API 参考
### Tabs
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ----------------------------------------------------------------------- | -------------- | ----------------------------------------- |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉样式变体。Primary 使用填充指示器,Secondary 使用下划线指示器 |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | 标签布局方向 |
| `align` | `"start" \| "center" \| "end"` | `"center"` | 每个标签内部内容的对齐方式 |
| `selectedKey` | `string` | - | 受控选中标签的 key |
| `defaultSelectedKey` | `string` | - | 默认选中标签的 key |
| `onSelectionChange` | `(key: Key) => void` | - | 选中变化回调 |
| `className` | `string` | - | 附加 CSS 类 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Tabs.List
| Prop | 类型 | 默认值 | 描述 |
| ------------ | -------------------------------------------------------------------------- | --- | -------------------------- |
| `aria-label` | `string` | - | 标签列表的无障碍标签 |
| `className` | `string` | - | 附加 CSS 类 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Tabs.Tab
| Prop | 类型 | 默认值 | 描述 |
| ------------ | ---------------------------------------------------------------------- | ------- | -------------------------- |
| `id` | `string` | - | 标签唯一标识 |
| `isDisabled` | `boolean` | `false` | 是否禁用该标签 |
| `className` | `string` | - | 附加 CSS 类 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### Tabs.Separator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
### Tabs.Panel
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------------------------------------------------------------------- | --- | -------------------------- |
| `id` | `string` | - | 与对应 Tab id 匹配的面板标识 |
| `className` | `string` | - | 附加 CSS 类 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
## 相关案例
## 相关组件
## Related Components
* **Breadcrumbs**: Display the user's current location within a hierarchy
# AlertDialog 警告对话框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/alert-dialog
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(overlays)/alert-dialog.mdx
> 用于关键确认操作的模态对话框,需要用户关注并明确操作
## 用法
```tsx
import { AlertDialog } from "@heroui/react";
```
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function Default() {
return (
删除项目
要永久删除项目吗?
此操作将永久删除 我的精彩项目 及其全部数据,且无法撤销。
取消
删除项目
);
}
```
## 组件结构
```tsx
import {AlertDialog, Button} from "@heroui/react";
export default () => (
Open Alert Dialog
{/* Optional: Close button */}
{/* Optional: Status icon */}
);
```
## 示例
### 状态类型
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function Statuses() {
const examples = [
{
actions: {
cancel: "保持登录",
confirm: "退出登录",
},
body: "退出后需要重新登录才能访问账户,未保存的更改将丢失。",
classNames: "bg-accent-soft text-accent-soft-foreground",
header: "要退出当前账户吗?",
status: "accent",
trigger: "退出登录",
},
{
actions: {
cancel: "稍后再说",
confirm: "标记完成",
},
body: "将把该任务标记为完成并通知所有成员,任务会移入已完成列表。",
classNames: "bg-success-soft text-success-soft-foreground",
header: "要完成此任务吗?",
status: "success",
trigger: "完成任务",
},
{
actions: {
cancel: "继续编辑",
confirm: "放弃更改",
},
body: "你有未保存的更改,放弃后将永久丢失。确定要放弃吗?",
classNames: "bg-warning-soft text-warning-soft-foreground",
header: "要放弃未保存的更改吗?",
status: "warning",
trigger: "放弃更改",
},
{
actions: {
cancel: "取消",
confirm: "删除账户",
},
body: "将永久删除你的账户并从服务器移除全部数据,此操作不可恢复。",
classNames: "bg-danger-soft text-danger-soft-foreground",
header: "要删除账户吗?",
status: "danger",
trigger: "删除账户",
},
] as const;
return (
{examples.map(({actions, body, classNames, header, status, trigger}) => (
{trigger}
{header}
{body}
{actions.cancel}
{actions.confirm}
))}
);
}
```
### 弹出位置
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
const PLACEMENT_LABELS = {
auto: "自动",
bottom: "底部",
center: "居中",
top: "顶部",
} as const;
export function Placements() {
const placements = ["auto", "top", "center", "bottom"] as const;
return (
{placements.map((placement) => (
{PLACEMENT_LABELS[placement]}
{placement === "auto" ? "自动定位" : `${PLACEMENT_LABELS[placement]}位置`}
{placement === "auto"
? "在移动端默认靠近底部,在桌面端居中,以获得更合适的阅读与操作体验。"
: `对话框将锚定在视口的「${PLACEMENT_LABELS[placement]}」区域。重要确认通常使用居中 placement 以吸引最多注意。`}
取消
确认
))}
);
}
```
### 尺寸
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
const SIZE_LABELS = {
cover: "通栏",
lg: "大",
md: "中",
sm: "小",
xs: "超小",
} as const;
export function Sizes() {
const sizes = ["xs", "sm", "md", "lg", "cover"] as const;
return (
{sizes.map((size) => (
{SIZE_LABELS[size]}
尺寸:{SIZE_LABELS[size]}
{size === "cover" ? (
<>
此警告框使用 cover 尺寸:在移动端与桌面端保留边距(移动端约
16px、桌面端约
40px)铺满可视区域,仍保持圆角与标准内边距,适合需要最大宽度又保留对话框气质的关键确认。
>
) : (
<>
此警告框使用 {size}{" "}
尺寸。在移动端各尺寸都会接近全宽以便阅读;在桌面端则对应不同的最大宽度,以适配不同信息量。
>
)}
取消
确认
))}
);
}
```
### 受控模式
```tsx
"use client";
import {AlertDialog, Button, useOverlayState} from "@heroui/react";
import React from "react";
export function Controlled() {
const [isOpen, setIsOpen] = React.useState(false);
const state = useOverlayState();
return (
配合 React.useState()
使用 React 的 useState{" "}
管理对话框开关,适合简单场景。
状态:{" "}
{isOpen ? "打开" : "关闭"}
setIsOpen(true)}>
打开对话框
setIsOpen(!isOpen)}>
切换
由 useState() 控制
该警告对话框由 React 的 useState 控制。将 isOpen 与{" "}
onOpenChange 传入即可在外部管理状态。
取消
确认
配合 useOverlayState()
使用 useOverlayState 获得更简洁的 API,内置{" "}
open()、close()、toggle() 等方法。
状态:{" "}
{state.isOpen ? "打开" : "关闭"}
打开对话框
切换
由 useOverlayState() 控制
useOverlayState 为常见操作提供专用方法,无需手写回调,直接使用{" "}
state.open()、state.close() 或{" "}
state.toggle() 即可。
取消
确认
);
}
```
### 自定义图标
```tsx
"use client";
import {LockOpen} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function CustomIcon() {
return (
重置密码
要重置密码吗?
我们会向你的邮箱发送重置链接。你需要设置新密码以恢复账户访问。
取消
发送重置链接
);
}
```
### 自定义触发器
```tsx
"use client";
import {TrashBin} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function CustomTrigger() {
return (
要删除此条目吗?
使用 AlertDialog.Trigger{" "}
可在标准按钮之外自定义触发区域。此示例展示带图标与说明文字的卡片式触发器。
取消
删除条目
);
}
```
### 遮罩变体
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
const VARIANT_LABELS = {
blur: "模糊",
opaque: "不透明",
transparent: "透明",
} as const;
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
return (
{variants.map((variant) => (
{VARIANT_LABELS[variant]}
背景:{VARIANT_LABELS[variant]}
{variant === "opaque"
? "不透明的深色背景会完全遮挡背后内容,让用户把注意力集中在对话框上。"
: variant === "blur"
? "模糊背景会柔和地虚化背后内容,同时保留一定的环境上下文。"
: "透明背景会完整保留背后内容,适合重要性较低的确认场景。"}
取消
确认
))}
);
}
```
### 自定义遮罩
```tsx
"use client";
import {TriangleExclamation} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function CustomBackdrop() {
return (
删除账户
要永久删除账户吗?
此操作无法撤销。你的数据、设置与内容将从服务器永久清除。醒目的红色背景用于强调该决定的严重性与不可逆性。
保留账户
永久删除
);
}
```
### 关闭行为
```tsx
"use client";
import {CircleInfo} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
export function DismissBehavior() {
return (
isDismissable
控制是否允许通过点击遮罩关闭对话框。警告框通常需要明确操作,因此默认为 false
。对重要性较低的确认,可设为 true。
打开警告对话框
isDismissable = false
点击遮罩不会关闭此对话框
尝试点击遮罩区域——对话框不会关闭,必须通过底部操作按钮关闭。
取消
确认
isKeyboardDismissDisabled
控制是否允许通过 ESC 关闭。警告框通常需要明确操作,因此默认为 true(禁用
ESC)。设为 false 时将允许 ESC 关闭。
打开警告对话框
isKeyboardDismissDisabled = true
已禁用 ESC 关闭
按下 ESC 不会有任何反应,必须通过操作按钮关闭此对话框。
取消
确认
);
}
```
### 关闭方式
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function CloseMethods() {
return (
使用 slot="close"
最简单的关闭方式:在对话框内的任意 Button 上添加{" "}
slot="close",点击后会自动关闭对话框。
打开对话框
使用 slot="close"
点击下方任一按钮——它们都带有 slot="close"
,点击后会自动关闭对话框。
取消
确认
使用 Dialog 的 render props
通过 Dialog 的 render props 获取 close{" "}
方法,从而完全控制关闭时机与方式,便于在关闭前加入校验等自定义逻辑。
打开对话框
{(renderProps) => (
<>
使用 Dialog render props
下方按钮使用 render props 提供的 close 方法。你可以在调用{" "}
renderProps.close() 之前加入校验或其他逻辑。
renderProps.close()}>
取消
renderProps.close()}>确认
>
)}
);
}
```
### 自定义动画
```tsx
"use client";
import {ArrowUpFromLine, Sparkles} from "@gravity-ui/icons";
import {AlertDialog, Button} from "@heroui/react";
import React from "react";
const iconMap: Record> = {
"gravity-ui:arrow-up-from-line": ArrowUpFromLine,
"gravity-ui:sparkles": Sparkles,
};
export function CustomAnimations() {
const animations = [
{
classNames: {
backdrop: [
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:zoom-in-95",
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:zoom-out-95",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
},
description:
"基于物理感的弹性缩放,模拟高阻尼弹簧:瞬态响应快、回落时间长,适合警告框与模态框。",
icon: "gravity-ui:sparkles",
name: "运动学缩放",
},
{
classNames: {
backdrop: [
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:slide-in-from-bottom-4",
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:slide-out-to-bottom-2",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
},
description:
"模拟在介质中运动并受流体阻力影响,避免机械式线性,更自然、更贴地,适合底部抽屉或 Toast。",
icon: "gravity-ui:arrow-up-from-line",
name: "流体滑入",
},
];
return (
{animations.map(({classNames, description, icon, name}) => {
const IconComponent = iconMap[icon];
return (
{name}
{!!IconComponent && }
{name} 动画
{description}
关闭
再试一次
);
})}
);
}
```
### 自定义 Portal
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
import {useCallback, useRef, useState} from "react";
export function CustomPortal() {
const portalRef = useRef(null);
const [portalContainer, setPortalContainer] = useState(null);
const setPortalRef = useCallback((node: HTMLDivElement | null) => {
portalRef.current = node;
setPortalContainer(node);
}, []);
return (
将警告对话框渲染到自定义容器,而不是 document.body
为容器应用 transform: translateZ(0){" "}
可创建新的层叠上下文。
{!!portalContainer && (
打开警告对话框
自定义传送门
此段为示例占位文案,用于演示在自定义容器内渲染对话框时的滚动与排版效果。实际项目中请替换为真实说明内容。
通过将浮层挂载到局部容器,可以配合裁剪、缩放或卡片布局,避免遮挡整个页面,同时仍保持焦点管理与无障碍行为。
若容器存在 transform 或 filter{" "}
等属性,请注意浏览器会为其创建新的包含块,从而影响定位与层级关系。
取消
确认
)}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {AlertDialog, Button} from "@heroui/react";
export function CustomStyles() {
return (
退出登录
确定要退出账户吗?
您将在此设备上退出登录。除非已保存到云端,否则{" "}
Acme 工作区 中未保存的工作可能会丢失。
保持登录
退出登录
);
}
```
### 全局 CSS
若要自定义 AlertDialog 组件类,可使用 `@layer components` 指令。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.alert-dialog__backdrop {
@apply bg-gradient-to-br from-black/60 to-black/80;
}
.alert-dialog__dialog {
@apply rounded-2xl border border-red-500/20 shadow-2xl;
}
.alert-dialog__header {
@apply gap-4;
}
.alert-dialog__icon {
@apply size-16;
}
.alert-dialog__close-trigger {
@apply rounded-full bg-white/10 hover:bg-white/20;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
AlertDialog 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/alert-dialog.css)):
#### 基础类 \[!toc]
* `.alert-dialog__trigger` - 打开警告对话框的触发元素
* `.alert-dialog__backdrop` - 对话框背后的遮罩层
* `.alert-dialog__container` - 支持 placement 的定位包裹层
* `.alert-dialog__dialog` - 对话框内容容器
* `.alert-dialog__header` - 图标与标题的头部区域
* `.alert-dialog__heading` - 标题文本样式
* `.alert-dialog__body` - 主内容区域
* `.alert-dialog__footer` - 操作按钮的底部区域
* `.alert-dialog__icon` - 带状态色的图标容器
* `.alert-dialog__close-trigger` - 关闭按钮元素
#### 遮罩变体 \[!toc]
* `.alert-dialog__backdrop--opaque` - 不透明彩色遮罩(默认)
* `.alert-dialog__backdrop--blur` - 带玻璃效果的模糊遮罩
* `.alert-dialog__backdrop--transparent` - 透明遮罩(无覆盖层)
#### 状态变体(图标)\[!toc]
* `.alert-dialog__icon--default` - 默认灰色状态
* `.alert-dialog__icon--accent` - 强调蓝色状态
* `.alert-dialog__icon--success` - 成功绿色状态
* `.alert-dialog__icon--warning` - 警告橙色状态
* `.alert-dialog__icon--danger` - 危险红色状态
### 交互状态
该组件支持以下交互状态:
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]` — 应用于 trigger、dialog 和 close 按钮
* **Hover**:`:hover` 或 `[data-hovered="true"]` — 应用于 close 按钮悬停时
* **Active**:`:active` 或 `[data-pressed="true"]` — 应用于 close 按钮按下时
* **Entering**:`[data-entering]` — 对话框打开动画期间
* **Exiting**:`[data-exiting]` — 对话框关闭动画期间
* **Placement**:`[data-placement="*"]` — 根据对话框位置应用(auto、top、center、bottom)
## API 参考
### AlertDialog
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ----------- | --- | -------- |
| `children` | `ReactNode` | - | 触发器与容器元素 |
### AlertDialog.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | -------- |
| `children` | `ReactNode` | - | 自定义触发器内容 |
| `className` | `string` | - | CSS 类 |
### AlertDialog.Backdrop
| Prop | 类型 | 默认值 | 描述 |
| --------------------------- | ------------------------------------- | ---------- | ------------- |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | 遮罩覆盖层样式 |
| `isDismissable` | `boolean` | `false` | 点击遮罩是否关闭 |
| `isKeyboardDismissDisabled` | `boolean` | `true` | 是否禁用 ESC 键关闭 |
| `isOpen` | `boolean` | - | 受控的打开状态 |
| `onOpenChange` | `(isOpen: boolean) => void` | - | 打开状态变化处理函数 |
| `className` | `string \| (values) => string` | - | 遮罩 CSS 类 |
| `UNSTABLE_portalContainer` | `HTMLElement` | - | 自定义 portal 容器 |
### AlertDialog.Container
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------------------- | -------- | ----------------- |
| `placement` | `"auto" \| "center" \| "top" \| "bottom"` | `"auto"` | 对话框在屏幕上的位置 |
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "cover"` | `"md"` | Alert Dialog 尺寸变体 |
| `className` | `string \| (values) => string` | - | 容器 CSS 类 |
### AlertDialog.Dialog
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | ------------------------------------- | --------------- | --------- |
| `children` | `ReactNode \| ({close}) => ReactNode` | - | 内容或渲染函数 |
| `className` | `string` | - | CSS 类 |
| `role` | `string` | `"alertdialog"` | ARIA role |
| `aria-label` | `string` | - | 无障碍标签 |
| `aria-labelledby` | `string` | - | 标签元素的 ID |
| `aria-describedby` | `string` | - | 描述元素的 ID |
### AlertDialog.Header
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------------ |
| `children` | `ReactNode` | - | 头部内容(通常为 Icon 和 Heading) |
| `className` | `string` | - | CSS 类 |
### AlertDialog.Heading
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ----- |
| `children` | `ReactNode` | - | 标题文本 |
| `className` | `string` | - | CSS 类 |
### AlertDialog.Body
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ----- |
| `children` | `ReactNode` | - | 正文内容 |
| `className` | `string` | - | CSS 类 |
### AlertDialog.Footer
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------- |
| `children` | `ReactNode` | - | 底部内容(通常为操作按钮) |
| `className` | `string` | - | CSS 类 |
### AlertDialog.Icon
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------- | ---------- | ------- |
| `children` | `ReactNode` | - | 自定义图标元素 |
| `status` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"danger"` | 状态颜色变体 |
| `className` | `string` | - | CSS 类 |
### AlertDialog.CloseTrigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------ | --- | ------- |
| `children` | `ReactNode` | - | 自定义关闭按钮 |
| `className` | `string \| (values) => string` | - | CSS 类 |
### useOverlayState Hook
```tsx
import {useOverlayState} from "@heroui/react";
const state = useOverlayState({
defaultOpen: false,
onOpenChange: (isOpen) => console.log(isOpen),
});
state.isOpen; // Current state
state.open(); // Open dialog
state.close(); // Close dialog
state.toggle(); // Toggle state
state.setOpen(); // Set state directly
```
## 无障碍
实现 [WAI-ARIA AlertDialog 模式](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/):
* **焦点陷阱**:焦点锁定在警告对话框内
* **键盘**:`ESC` 关闭(启用时)、`Tab` 循环切换元素
* **屏幕阅读器**:使用 `role="alertdialog"` 的正确 ARIA 属性
* **滚动锁定**:打开时禁用 body 滚动
* **必需操作**:默认需要用户明确操作(不可通过遮罩/ESC 关闭)
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **CloseButton**: Button for dismissing overlays
# Drawer 抽屉
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/drawer
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(overlays)/drawer.mdx
> 用于补充内容与操作的滑出面板
## 用法
```tsx
import { Drawer, Button } from "@heroui/react";
```
```tsx
import {Button, Drawer} from "@heroui/react";
export function Basic() {
return (
打开抽屉
抽屉标题
这是一个基于 React Aria Modal 组件构建的抽屉。它会从屏幕边缘滑入,并通过流畅的 CSS
过渡呈现动画效果。
取消
确认
);
}
```
## 组件结构
```tsx
import { Drawer, Button } from "@heroui/react";
export default () => (
Open Drawer
{/* Optional: Drag handle */}
{/* Optional: Close button */}
);
```
## 示例
### 弹出位置
```tsx
import {Button, Drawer} from "@heroui/react";
const PLACEMENT_LABELS = {
bottom: "底部",
left: "左侧",
right: "右侧",
top: "顶部",
} as const;
export function Placements() {
const placements = ["bottom", "top", "left", "right"] as const;
return (
{placements.map((placement) => (
{PLACEMENT_LABELS[placement]}
{placement === "bottom" && }
{PLACEMENT_LABELS[placement]}抽屉
此抽屉从屏幕{PLACEMENT_LABELS[placement]} 边缘滑入。
取消
完成
{placement === "top" && }
))}
);
}
```
### 不可关闭
在 `Drawer.Backdrop` 上设置 `isDismissable={false}` 可阻止点击外部或拖拽关闭。用户必须通过抽屉内的操作按钮交互。
```tsx
import {Button, Drawer} from "@heroui/react";
export function NonDismissable() {
return (
重要操作
确认操作
此抽屉无法通过点击外部或拖拽关闭。你必须使用下方按钮之一来完成操作。
取消
确认
);
}
```
### 可滚动内容
`Drawer.Body` 通过原生滚动自动处理溢出。拖拽关闭在 body 区域被排除,以避免与滚动冲突。
```tsx
import {Button, Drawer} from "@heroui/react";
export function ScrollableContent() {
return (
条款与条件
条款与条件
{Array.from({length: 20}).map((_, i) => (
段落 {i + 1}:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit
risus, sed porttitor quam.
))}
拒绝
接受
);
}
```
### 受控模式
```tsx
"use client";
import {Button, Drawer, useOverlayState} from "@heroui/react";
import React from "react";
export function Controlled() {
const [isOpen, setIsOpen] = React.useState(false);
const state = useOverlayState();
return (
配合 React.useState()
使用 React 的 useState{" "}
管理抽屉开关,适合简单场景。
状态:{" "}
{isOpen ? "打开" : "关闭"}
setIsOpen(true)}>
打开抽屉
setIsOpen(!isOpen)}>
切换
由 useState() 控制
该抽屉由 React 的 useState 控制。将 isOpen 与{" "}
onOpenChange 传入即可在外部管理状态。
关闭
配合 useOverlayState()
使用 useOverlayState 获得更简洁的 API,内置{" "}
open()、close()、toggle() 等方法。
状态:{" "}
{state.isOpen ? "打开" : "关闭"}
打开抽屉
切换
由 useOverlayState() 控制
useOverlayState 为常见操作提供专用方法,无需手写回调,直接使用{" "}
state.open()、state.close() 或{" "}
state.toggle() 即可。
关闭
);
}
```
### 搭配表单
```tsx
import {Button, Drawer, Input, Label, TextField} from "@heroui/react";
export function WithForm() {
return (
编辑资料
编辑资料
姓名
邮箱
简介
取消
保存更改
);
}
```
### 导航抽屉
```tsx
import type {ComponentType, SVGProps} from "react";
import {Bars, Bell, Envelope, Gear, House, Magnifier, Person} from "@gravity-ui/icons";
import {Button, Drawer} from "@heroui/react";
export function Navigation() {
const navItems: {icon: ComponentType>; label: string}[] = [
{icon: House, label: "首页"},
{icon: Magnifier, label: "搜索"},
{icon: Bell, label: "通知"},
{icon: Envelope, label: "消息"},
{icon: Person, label: "个人资料"},
{icon: Gear, label: "设置"},
];
return (
菜单
导航
{navItems.map((item) => (
{item.label}
))}
);
}
```
### 遮罩变体
```tsx
import {Button, Drawer} from "@heroui/react";
const VARIANT_LABELS = {
blur: "模糊",
opaque: "不透明",
transparent: "透明",
} as const;
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
return (
{variants.map((variant) => (
{VARIANT_LABELS[variant]}
背景:{VARIANT_LABELS[variant]}
此抽屉使用 {variant} 背景变体。
关闭
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Button, Drawer} from "@heroui/react";
export function CustomStyles() {
return (
打开筛选
筛选
按状态、负责人或日期缩小结果范围。
取消
应用
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Drawer 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.drawer__backdrop {
@apply bg-gradient-to-br from-black/50 to-black/70;
}
.drawer__dialog {
@apply rounded-2xl border border-white/10 shadow-2xl;
}
.drawer__header {
@apply text-center;
}
.drawer__close-trigger {
@apply rounded-full bg-white/10 hover:bg-white/20;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Drawer 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/drawer.css)):
#### 基础类 \[!toc]
* `.drawer__trigger` - 打开抽屉的触发元素
* `.drawer__backdrop` - 抽屉背后的遮罩层
* `.drawer__content` - 抽屉面板的定位包裹层
* `.drawer__dialog` - 抽屉面板本身
* `.drawer__header` - 标题区域
* `.drawer__heading` - 主标题文本
* `.drawer__body` - 主可滚动内容区域
* `.drawer__footer` - 操作按钮区域
* `.drawer__handle` - 视觉拖拽手柄指示器
* `.drawer__close-trigger` - 关闭按钮元素
#### 遮罩变体 \[!toc]
* `.drawer__backdrop--opaque` - 不透明彩色遮罩(默认)
* `.drawer__backdrop--blur` - 模糊遮罩,玻璃效果
* `.drawer__backdrop--transparent` - 透明遮罩(无覆盖层)
#### 位置变体 \[!toc]
* `.drawer__content--bottom` - 从底部边缘滑入(默认)
* `.drawer__content--top` - 从顶部边缘滑入
* `.drawer__content--left` - 从左侧边缘滑入
* `.drawer__content--right` - 从右侧边缘滑入
#### 对话框变体 \[!toc]
* `.drawer__dialog--top` - 从顶部边缘滑入
* `.drawer__dialog--bottom` - 从底部边缘滑入
* `.drawer__dialog--left` - 从左侧边缘滑入
* `.drawer__dialog--right` - 从右侧边缘滑入
### 交互状态
组件支持以下交互状态:
* **Focus**:`:focus-visible` 或 `[data-focus-visible="true"]` - 应用于触发器与关闭按钮
* **Hover**:`:hover` 或 `[data-hovered="true"]` - 悬停时应用于关闭按钮
* **Active**:`:active` 或 `[data-pressed="true"]` - 按下时应用于触发器与关闭按钮
* **Entering**:`[data-entering]` - 抽屉打开动画期间
* **Exiting**:`[data-exiting]` - 抽屉关闭动画期间
* **Placement**:`[data-placement="*"]` - 根据抽屉位置(top、bottom、left、right)应用
## API 参考
### Drawer
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ----------------------- | --- | ---------------- |
| `children` | `ReactNode` | - | 触发器与 backdrop 元素 |
| `state` | `UseOverlayStateReturn` | - | 受控 overlay 状态 |
### Drawer.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | -------- |
| `children` | `ReactNode` | - | 自定义触发器内容 |
| `className` | `string` | - | CSS 类 |
### Drawer.Backdrop
| Prop | 类型 | 默认值 | 描述 |
| --------------------------- | ------------------------------------- | ---------- | -------------- |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | 遮罩覆盖层样式 |
| `isDismissable` | `boolean` | `true` | 点击 backdrop 关闭 |
| `isKeyboardDismissDisabled` | `boolean` | `false` | 禁用 ESC 关闭 |
| `isOpen` | `boolean` | - | 受控打开状态 |
| `onOpenChange` | `(isOpen: boolean) => void` | - | 打开状态变化处理函数 |
| `className` | `string \| (values) => string` | - | Backdrop CSS 类 |
### Drawer.Content
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ---------------------------------------- | ---------- | ------------- |
| `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | 抽屉滑入的边缘 |
| `className` | `string \| (values) => string` | - | Content CSS 类 |
### Drawer.Dialog
| Prop | 类型 | 默认值 | 描述 |
| ----------------- | ----------- | ---------- | --------- |
| `children` | `ReactNode` | - | Dialog 内容 |
| `className` | `string` | - | CSS 类 |
| `role` | `string` | `"dialog"` | ARIA role |
| `aria-label` | `string` | - | 无障碍标签 |
| `aria-labelledby` | `string` | - | 标签元素 ID |
### Drawer.Header
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------- |
| `children` | `ReactNode` | - | Header 内容 |
| `className` | `string` | - | CSS 类 |
### Drawer.Heading
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ----- |
| `children` | `ReactNode` | - | 标题文本 |
| `className` | `string` | - | CSS 类 |
### Drawer.Body
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------- |
| `children` | `ReactNode` | - | Body 内容 |
| `className` | `string` | - | CSS 类 |
### Drawer.Footer
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------- |
| `children` | `ReactNode` | - | Footer 内容 |
| `className` | `string` | - | CSS 类 |
### Drawer.Handle
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------- | --- | ----- |
| `className` | `string` | - | CSS 类 |
### Drawer.CloseTrigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------ | --- | ------- |
| `children` | `ReactNode` | - | 自定义关闭按钮 |
| `className` | `string \| (values) => string` | - | CSS 类 |
### useOverlayState Hook
```tsx
import { useOverlayState } from "@heroui/react";
const state = useOverlayState({
defaultOpen: false,
onOpenChange: (isOpen) => console.log(isOpen),
});
state.isOpen; // Current state
state.open(); // Open drawer
state.close(); // Close drawer
state.toggle(); // Toggle state
state.setOpen(); // Set state directly
```
## 无障碍
实现 [WAI-ARIA Dialog 模式](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/):
* **焦点陷阱**:打开时焦点锁定在抽屉内
* **键盘**:`ESC` 关闭(可关闭时),`Tab` 循环元素
* **屏幕阅读器**:通过 React Aria 提供正确的 ARIA 属性
* **滚动锁定**:打开时禁用 body 滚动
* **拖拽关闭**:在 handle、header 和 footer 区域支持指针拖拽手势
## 相关组件
## Related Components
* **Modal**: Displays content in a modal overlay
* **Button**: Allows a user to perform an action
* **CloseButton**: Button for dismissing overlays
# Modal 模态框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/modal
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(overlays)/modal.mdx
> 用于聚焦用户交互与重要内容的对话框遮罩层。
## 用法
```tsx
import { Modal } from "@heroui/react";
```
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function Default() {
return (
打开模态框
欢迎使用 HeroUI
一套美观、快速、现代的 React UI 库,可轻松构建无障碍且高度可定制的 Web 应用。
继续
);
}
```
## 组件结构
导入 Modal 组件后,可通过点语法访问所有子部分。
```tsx
import {Modal, Button} from "@heroui/react";
export default () => (
Open Modal
{/* Optional: Close button */}
{/* Optional: Icon */}
);
```
## 示例
### 尺寸
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
const SIZE_LABELS = {
cover: "通栏",
full: "全屏",
lg: "大",
md: "中",
sm: "小",
xs: "超小",
} as const;
export function Sizes() {
const sizes = ["xs", "sm", "md", "lg", "cover", "full"] as const;
return (
{sizes.map((size) => (
{SIZE_LABELS[size]}
尺寸:{SIZE_LABELS[size]}
{size === "cover" ? (
<>
此模态框使用 cover 尺寸:在移动端与桌面端保留边距(移动端约
16px、桌面端约
40px)铺满可视区域,仍保持圆角与标准内边距,适合需要最大宽度又保留模态框气质的内容展示。
>
) : size === "full" ? (
<>
此模态框使用 full{" "}
尺寸,占满整个视口,无边距、圆角或阴影,提供真正的全屏体验,适合沉浸式内容或全页交互。
>
) : (
<>
此模态框使用 {size}{" "}
尺寸。在移动端各尺寸都会接近全宽以便阅读;在桌面端则对应不同的最大宽度,以适配不同信息量。
>
)}
取消
确认
))}
);
}
```
### 位置
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
const PLACEMENT_LABELS = {
auto: "自动",
bottom: "底部",
center: "居中",
top: "顶部",
} as const;
export function Placements() {
const placements = ["auto", "top", "center", "bottom"] as const;
return (
{placements.map((placement) => (
{PLACEMENT_LABELS[placement]}
{placement === "auto" ? "自动定位" : `${PLACEMENT_LABELS[placement]}位置`}
{placement === "auto"
? "在移动端默认靠近底部,在桌面端居中,以获得更合适的阅读与操作体验。"
: `模态框将锚定在视口的「${PLACEMENT_LABELS[placement]}」区域。可尝试不同 placement 查看屏幕上的定位效果。`}
继续
))}
);
}
```
### 滚动行为
```tsx
"use client";
import {Button, Modal, Radio, RadioGroup} from "@heroui/react";
import {useState} from "react";
export function ScrollComparison() {
const [scroll, setScroll] = useState<"inside" | "outside">("inside");
return (
setScroll(value as "inside" | "outside")}
>
内部
外部
打开模态框({scroll.charAt(0).toUpperCase() + scroll.slice(1)})
{scroll === "inside" ? "滚动:内部" : "滚动:外部"}
对比滚动行为——内部在模态框内滚动内容,外部允许页面滚动
{Array.from({length: 30}).map((_, i) => (
段落 {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit
risus, sed porttitor quam.
))}
取消
确认
);
}
```
### 受控状态
```tsx
"use client";
import {CircleCheck} from "@gravity-ui/icons";
import {Button, Modal, useOverlayState} from "@heroui/react";
import React from "react";
export function Controlled() {
const [isOpen, setIsOpen] = React.useState(false);
const state = useOverlayState();
return (
配合 React.useState()
使用 React 的 useState{" "}
管理模态框开关,适合简单场景。
状态:{" "}
{isOpen ? "打开" : "关闭"}
setIsOpen(true)}>
打开模态框
setIsOpen(!isOpen)}>
切换
由 useState() 控制
该模态框由 React 的 useState 控制。将 isOpen 与{" "}
onOpenChange 传入即可在外部管理状态。
取消
确认
配合 useOverlayState()
使用 useOverlayState 获得更简洁的 API,内置{" "}
open()、close()、toggle() 等方法。
状态:{" "}
{state.isOpen ? "打开" : "关闭"}
打开模态框
切换
由 useOverlayState() 控制
useOverlayState 为常见操作提供专用方法,无需手写回调,直接使用{" "}
state.open()、state.close() 或{" "}
state.toggle() 即可。
取消
确认
);
}
```
### 搭配表单
```tsx
"use client";
import {Envelope} from "@gravity-ui/icons";
import {Button, Input, Label, Modal, Surface, TextField} from "@heroui/react";
export function WithForm() {
return (
打开联系表单
联系我们
填写下方表单,我们会尽快回复。在移动端弹出键盘时,模态框会自动适配。
姓名
邮箱
电话
公司
留言
取消
发送消息
);
}
```
### 自定义触发器
```tsx
"use client";
import {Gear} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CustomTrigger() {
return (
设置
使用 Modal.Trigger{" "}
可在标准按钮之外创建自定义触发器。此示例展示带图标与说明文字的卡片式触发器。
取消
保存
);
}
```
### 遮罩变体
```tsx
"use client";
import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
const VARIANT_LABELS = {
blur: "模糊",
opaque: "不透明",
transparent: "透明",
} as const;
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
return (
{variants.map((variant) => (
{VARIANT_LABELS[variant]}
背景:{VARIANT_LABELS[variant]}
{variant === "opaque"
? "不透明背景会完全遮挡背后内容,让用户把注意力集中在模态框上。"
: variant === "blur"
? "模糊背景会柔和地虚化背后内容,同时保留一定的环境上下文。"
: "透明背景会完整保留背后内容,适合重要性较低的交互场景。"}
继续
))}
);
}
```
### 自定义遮罩
```tsx
"use client";
import {Sparkles} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CustomBackdrop() {
return (
自定义背景
高级背景
此背景采用从底部深色过渡到顶部完全透明的精致渐变,并配合柔和的模糊效果。渐变会在浅色与深色模式下自动调整强度,以获得最佳对比度。
Amazing!
关闭
);
}
```
### 关闭行为
```tsx
"use client";
import {CircleInfo} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function DismissBehavior() {
return (
isDismissable
控制是否允许通过点击遮罩关闭模态框。默认为 true。设为 false{" "}
时需通过明确操作关闭。
打开模态框
isDismissable = false
点击遮罩不会关闭此模态框
尝试点击遮罩区域——模态框不会关闭,必须使用关闭按钮或按 ESC 键关闭。
关闭
isKeyboardDismissDisabled
控制是否允许通过 ESC 关闭模态框。设为 true 时将禁用
ESC,用户须通过明确操作关闭。
打开模态框
isKeyboardDismissDisabled = true
已禁用 ESC 键
按 ESC 无反应。必须使用关闭按钮或点击遮罩才能关闭此模态框。
关闭
);
}
```
### 关闭方式
```tsx
"use client";
import {CircleCheck, CircleInfo} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CloseMethods() {
return (
使用 slot="close"
关闭模态框的最简方式:为模态框内任意 Button 添加 slot="close"
,点击即可自动关闭。
打开模态框
使用 slot="close"
点击下方任一按钮——它们都带有 slot="close",会自动关闭模态框。
取消
确认
使用 Dialog 渲染属性
通过 Dialog 的渲染属性访问 close{" "}
方法,可完全控制关闭时机与方式,并在关闭前加入自定义逻辑。
打开模态框
{(renderProps) => (
<>
使用 Dialog 渲染属性
下方按钮使用渲染属性中的 close 方法。可在调用{" "}
renderProps.close() 前进行校验或其他逻辑。
renderProps.close()}>
取消
renderProps.close()}>确认
>
)}
);
}
```
### 自定义动画
```tsx
"use client";
import {ArrowUpFromLine, Sparkles} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
import React from "react";
const iconMap: Record> = {
"gravity-ui:arrow-up-from-line": ArrowUpFromLine,
"gravity-ui:sparkles": Sparkles,
};
export function CustomAnimations() {
const animations = [
{
classNames: {
backdrop: [
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:zoom-in-95",
"data-[entering]:duration-400",
"data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:zoom-out-95",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]",
].join(" "),
},
description:
"基于物理的弹性缩放,模拟高阻尼弹簧系统:快速瞬态响应与较长 settling 时间。适用于模态框与弹出层。",
icon: "gravity-ui:sparkles",
name: "运动缩放",
},
{
classNames: {
backdrop: [
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
container: [
"data-[entering]:animate-in",
"data-[entering]:fade-in-0",
"data-[entering]:slide-in-from-bottom-4",
"data-[entering]:duration-500",
"data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]",
"data-[exiting]:animate-out",
"data-[exiting]:fade-out-0",
"data-[exiting]:slide-out-to-bottom-2",
"data-[exiting]:duration-200",
"data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]",
].join(" "),
},
description:
"模拟流体阻力中的运动,摆脱机械式线性动画,呈现更自然、沉稳的质感。适用于底部抽屉或 Toast。",
icon: "gravity-ui:arrow-up-from-line",
name: "流体滑入",
},
];
return (
{animations.map(({classNames, description, icon, name}) => {
const IconComponent = iconMap[icon];
return (
{name}
{!!IconComponent && }
{name} 动画
{description}
关闭
再试一次
);
})}
);
}
```
### 自定义 Portal
```tsx
"use client";
import {Button, Modal} from "@heroui/react";
import {useCallback, useRef, useState} from "react";
export function CustomPortal() {
const portalRef = useRef(null);
const [portalContainer, setPortalContainer] = useState(null);
const setPortalRef = useCallback((node: HTMLDivElement | null) => {
portalRef.current = node;
setPortalContainer(node);
}, []);
return (
在自定义容器内渲染模态框,而非 document.body
为容器应用 transform: translateZ(0){" "}
以创建新的层叠上下文。
{!!portalContainer && (
打开模态框
自定义 Portal
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
关闭
)}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import {CircleCheck} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
export function CustomStyles() {
return (
打开
更改已保存
你的草稿已在各设备间同步。
完成
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.modal__backdrop {
@apply bg-gradient-to-br from-black/50 to-black/70;
}
.modal__dialog {
@apply rounded-2xl border border-white/10 shadow-2xl;
}
.modal__header {
@apply text-center;
}
.modal__close-trigger {
@apply rounded-full bg-white/10 hover:bg-white/20;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Modal 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/modal.css)):
#### 基础类 \[!toc]
* `.modal__trigger` — 打开 Modal 的触发元素
* `.modal__backdrop` — Modal 背后的遮罩层
* `.modal__container` — 支持 placement 的定位包裹层
* `.modal__dialog` — Modal 内容容器
* `.modal__header` — 标题与图标区域
* `.modal__body` — 主内容区域
* `.modal__footer` — 操作区域
* `.modal__close-trigger` — 关闭按钮元素
#### 遮罩变体 \[!toc]
* `.modal__backdrop--opaque` — 不透明有色遮罩(默认)
* `.modal__backdrop--blur` — 带玻璃效果的模糊遮罩
* `.modal__backdrop--transparent` — 透明遮罩(无叠加层)
#### 滚动变体 \[!toc]
* `.modal__container--scroll-outside` — 允许整个 Modal 滚动
* `.modal__dialog--scroll-inside` — 限制 Modal 高度,由 body 区域滚动
* `.modal__body--scroll-inside` — 仅 body 区域可滚动
* `.modal__body--scroll-outside` — 允许整页滚动
### 交互状态
组件支持以下交互状态:
* **焦点**:`:focus-visible` 或 `[data-focus-visible="true"]` — 应用于触发器、对话框与关闭按钮
* **悬停**:`:hover` 或 `[data-hovered="true"]` — 应用于关闭按钮悬停
* **按下**:`:active` 或 `[data-pressed="true"]` — 应用于关闭按钮按下
* **进入**:`[data-entering]` — Modal 打开动画期间
* **离开**:`[data-exiting]` — Modal 关闭动画期间
* **位置**:`[data-placement="*"]` — 基于 Modal 位置(auto、top、center、bottom)
## API 参考
### Modal
| Prop | 类型 | 默认值 | 描述 |
| ---------- | ----------- | --- | -------- |
| `children` | `ReactNode` | - | 触发器与容器元素 |
### Modal.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------- |
| `children` | `ReactNode` | - | 自定义触发内容 |
| `className` | `string` | - | CSS 类 |
### Modal.Backdrop
| Prop | 类型 | 默认值 | 描述 |
| --------------------------- | ------------------------------------- | ---------- | ------------- |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | 遮罩叠加样式 |
| `isDismissable` | `boolean` | `true` | 点击遮罩是否关闭 |
| `isKeyboardDismissDisabled` | `boolean` | `false` | 是否禁用 ESC 关闭 |
| `isOpen` | `boolean` | - | 受控打开状态 |
| `onOpenChange` | `(isOpen: boolean) => void` | - | 打开状态变化处理函数 |
| `className` | `string \| (values) => string` | - | 遮罩 CSS 类 |
| `UNSTABLE_portalContainer` | `HTMLElement` | - | 自定义 portal 容器 |
### Modal.Container
| Prop | 类型 | 默认值 | 描述 |
| ----------- | --------------------------------------------------- | ---------- | ------------- |
| `placement` | `"auto" \| "center" \| "top" \| "bottom"` | `"auto"` | Modal 在屏幕上的位置 |
| `scroll` | `"inside" \| "outside"` | `"inside"` | 滚动行为 |
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "cover" \| "full"` | `"md"` | Modal 尺寸变体 |
| `className` | `string \| (values) => string` | - | 容器 CSS 类 |
### Modal.Dialog
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | ------------------------------------- | ---------- | --------- |
| `children` | `ReactNode \| ({close}) => ReactNode` | - | 内容或渲染函数 |
| `className` | `string \| (values) => string` | - | CSS 类 |
| `role` | `string` | `"dialog"` | ARIA role |
| `aria-label` | `string` | - | 无障碍标签 |
| `aria-labelledby` | `string` | - | 标签元素的 id |
| `aria-describedby` | `string` | - | 描述元素的 id |
### Modal.Header
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ----- |
| `children` | `ReactNode` | - | 头部内容 |
| `className` | `string` | - | CSS 类 |
### Modal.Body
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ----- |
| `children` | `ReactNode` | - | 正文内容 |
| `className` | `string` | - | CSS 类 |
### Modal.Footer
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ----- |
| `children` | `ReactNode` | - | 底部内容 |
| `className` | `string` | - | CSS 类 |
### Modal.CloseTrigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------ | --- | ------- |
| `children` | `ReactNode` | - | 自定义关闭按钮 |
| `className` | `string \| (values) => string` | - | CSS 类 |
### useOverlayState Hook
```tsx
import {useOverlayState} from "@heroui/react";
const state = useOverlayState({
defaultOpen: false,
onOpenChange: (isOpen) => console.log(isOpen),
});
state.isOpen; // 当前状态
state.open(); // 打开 modal
state.close(); // 关闭 modal
state.toggle(); // 切换状态
state.setOpen(); // 直接设置状态
```
## 无障碍
实现 [WAI-ARIA Dialog 模式](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/):
* **焦点陷阱**:焦点锁定在 Modal 内
* **键盘**:`ESC` 关闭(启用时)、`Tab` 在元素间循环
* **屏幕阅读器**:正确的 ARIA 属性
* **滚动锁定**:打开时禁用 body 滚动
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Tooltip**: Contextual information on hover or focus
* **Select**: Dropdown select control
# Popover 弹出框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/popover
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(overlays)/popover.mdx
> 在由按钮或任意自定义元素触发后,于 portal 中展示丰富内容。
## 用法
```tsx
import { Popover } from '@heroui/react';
```
```tsx
import {Button, Popover} from "@heroui/react";
export function PopoverBasic() {
return (
点击我
弹出层标题
这是弹出层内容,你可以在这里放置任何内容。
);
}
```
## 组件结构
```tsx
import { Popover } from '@heroui/react';
export default () => (
{/* content goes here */}
)
```
## 示例
### 带箭头
```tsx
import {Ellipsis} from "@gravity-ui/icons";
import {Button, Popover} from "@heroui/react";
export function PopoverWithArrow() {
return (
带箭头
带箭头的弹出层
箭头指向触发弹出层的元素。
带箭头的弹出层
箭头指向触发弹出层的元素。
);
}
```
### 可交互内容
```tsx
"use client";
import {Avatar, Button, Popover} from "@heroui/react";
import {useState} from "react";
export function PopoverInteractive() {
const [isFollowing, setIsFollowing] = useState(false);
return (
setIsFollowing(!isFollowing)}
>
{isFollowing ? "已关注" : "关注"}
产品设计师兼创意总监,打造有意义的美好体验。
);
}
```
### 位置
```tsx
import {Button, Popover} from "@heroui/react";
export function PopoverPlacement() {
return (
Top
顶部位置
Left
左侧位置
点击按钮
Right
右侧位置
Bottom
底部位置
);
}
```
### 渲染函数
```tsx
"use client";
import {Button, Popover} from "@heroui/react";
export function RenderFunction() {
return (
点击我
}
>
弹出层标题
这是弹出层内容,你可以在这里放置任何内容。
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Button, Popover} from "@heroui/react";
export function CustomStyles() {
return (
详情
键盘快捷键
保存
⌘ S
搜索
⌘ K
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.popover {
@apply rounded-xl shadow-2xl;
}
.popover__dialog {
@apply p-4;
}
.popover__heading {
@apply text-lg font-bold;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Popover 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/popover.css)):
#### 基础类 \[!toc]
* `.popover` - Popover 根容器样式
* `.popover__dialog` - 对话框内容包裹层
* `.popover__heading` - 标题文本样式
* `.popover__trigger` - 触发元素样式
### 交互状态
组件支持以下动画相关状态:
* **进入**:`[data-entering]` — Popover 出现过程中应用
* **离开**:`[data-exiting]` — Popover 消失过程中应用
* **位置**:`[data-placement="*"]` — 根据 Popover 位置应用
* **焦点**:`:focus-visible` 或 `[data-focus-visible="true"]`
## API 参考
### Popover
| Prop | 类型 | 默认值 | 描述 |
| -------------- | --------------------------- | ------- | ------------------- |
| `children` | `React.ReactNode` | - | 触发器与内容元素 |
| `isOpen` | `boolean` | - | 控制 Popover 是否可见(受控) |
| `defaultOpen` | `boolean` | `false` | 初始打开状态(非受控) |
| `onOpenChange` | `(isOpen: boolean) => void` | - | 打开状态变化时调用 |
### Popover.Content
| Prop | 类型 | 默认值 | 描述 |
| ------------ | -------------------------------------------------------------------------- | ---------- | ---------------------- |
| `children` | `React.ReactNode` | - | 在 Popover 中展示的内容 |
| `placement` | `"top" \| "bottom" \| "left" \| "right"` (及变体) | `"bottom"` | Popover 的位置 |
| `offset` | `number` | `8` | 与触发元素的距离 |
| `shouldFlip` | `boolean` | `true` | 是否允许 Popover 改变方向以适配空间 |
| `className` | `string` | - | 额外的 CSS 类名 |
| `render` | `DOMRenderFunction` | - | 通过自定义渲染函数覆盖默认的 DOM 元素。 |
### Popover.Dialog
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ---------- |
| `children` | `React.ReactNode` | - | 对话框内容 |
| `className` | `string` | - | 额外的 CSS 类名 |
### Popover.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------------- |
| `children` | `React.ReactNode` | - | 触发 Popover 的元素 |
| `className` | `string` | - | 额外的 CSS 类名 |
### Popover.Arrow
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------- | --- | ---------------------- |
| `children` | `React.ReactNode` | - | 自定义箭头元素 |
| `className` | `string` | - | 额外的 CSS 类名 |
| `render` | `DOMRenderFunction` | - | 通过自定义渲染函数覆盖默认的 DOM 元素。 |
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Tooltip**: Contextual information on hover or focus
* **Select**: Dropdown select control
# Sheet 面板
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/sheet
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(overlays)/sheet.mdx
> 支持原生滚动吸附、分段展开和嵌套的滑动面板
## 用法
```tsx
import {Button, Sheet} from "@heroui/react";
```
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function Basic() {
return (
打开面板
面板标题
这是一个具有流畅拖动关闭动画的底部面板。向下拖动手柄或点击关闭按钮即可关闭。
取消
确认
);
}
```
## 组件结构
```tsx
import {Button, Sheet} from "@heroui/react";
export default () => (
打开面板
详情
面板内容
完成
);
```
`Sheet.Root` 是 `Sheet` 的别名。将 `Sheet.Trigger` 直接放在根组件内,并包裹一个可按压元素(如 `Button`)。通过 `Sheet.Heading`、`aria-label` 或 `aria-labelledby` 为对话框提供无障碍名称。使用 `Sheet.Close`、`Sheet.CloseTrigger` 或带有 `slot="close"` 的按钮支持键盘关闭;拖动手柄对辅助技术隐藏。
## 示例
### 位置
根组件的 `placement` 支持 `bottom`(默认)、`top`、`left` 和 `right`。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function Placements() {
const placements = ["bottom", "top", "left", "right"] as const;
const labels = {bottom: "底部", left: "左侧", right: "右侧", top: "顶部"};
return (
{placements.map((p) => (
{labels[p]}
{p === "bottom" && }
{labels[p]}面板
此面板以流畅的动画从屏幕的{labels[p]} 滑入。
取消
完成
{p === "top" && }
))}
);
}
```
### 吸附点
数字表示**像素**,而不是比例。字符串表示 CSS 长度;百分比相对于面板尺寸。面板初始停留在第一个吸附点,完整展开位置始终隐式存在。末尾的 `1`、`"1"` 或 `"100%"` 会被忽略。其他数字仍以像素为单位。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const snapPointValues = ["148px", "355px"];
export function SnapPoints() {
return (
吸附点
吸附点
此面板初始显示 148px,并可吸附至
355px。拖过最后一个吸附点后,面板将完全展开,背景也会逐渐淡入。
{Array.from({length: 6}).map((_, i) => (
{i === 0
? "向上滑动以显示更多内容,并使背景逐渐淡入。"
: `此位置的更多内容(${i + 1})。`}
))}
);
}
```
### 连续吸附点
一次滑动可以跨越多个吸附点。完全展开位置隐式存在,无需额外的连续吸附属性。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const snapPointValues = ["148px", "355px"];
export function SnapPointsSequential() {
return (
连续吸附点
吸附点
一次滑动可以跨越多个吸附点。完全展开是手势的最终位置,因此无需将其单独列为吸附点。
);
}
```
### 吸附点(遮罩渐显)
经过部分展开的吸附点时,背景保持透明;越过最后一个吸附点并继续展开时,背景逐渐显现。此行为由 React Aria 管理,不支持 `fadeFromIndex`。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const fadeSnapPoints = ["150px", "300px", "450px"];
export function SnapPointsCustomFade() {
return (
背景渐变
背景渐变
经过各个吸附点时,背景保持透明。越过最后一个吸附点并继续完全展开面板时,背景才逐渐显现。
);
}
```
### 遮罩变体
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function BackdropVariants() {
const variants = ["opaque", "blur", "transparent"] as const;
const labels = {blur: "模糊", opaque: "不透明", transparent: "透明"};
return (
{variants.map((v) => (
{labels[v]}
背景:{labels[v]}
此面板使用 {v} 背景变体。
关闭
))}
);
}
```
### 不可关闭
`isDismissable={false}` 阻止点击外部、Escape 和滑动关闭。显式关闭按钮仍然可用。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function NonDismissable() {
return (
重要操作
确认操作
此面板无法通过点击外部或拖动关闭,请使用下方的按钮。
取消
确认
);
}
```
### 可滚动内容
在部分展开时,滑动会展开面板;完全展开后,`Sheet.Body` 才滚动内部内容。`Sheet.Handle` 也支持鼠标拖动。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function ScrollableContent() {
return (
条款与条件
条款与条件
{Array.from({length: 20}).map((_, i) => (
第 {i + 1}{" "}
段:使用本服务前,请仔细阅读相关条款与条件。继续使用即表示您已了解并同意这些条款。我们会持续改进服务,如有变更,将及时通知您。
))}
拒绝
接受
);
}
```
### 搭配表单
在 `Sheet.Body` 内组合表单字段。在面板表面使用次要样式的输入框。
```tsx
"use client";
import {Button, Input, Label, Sheet, TextField} from "@heroui/react";
export function WithForm() {
return (
编辑个人资料
编辑个人资料
姓名
邮箱
个人简介
取消
保存更改
);
}
```
### 悬浮面板
`isDetached` 在屏幕边缘与面板之间添加间距,并将所有角设为圆角。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function Detached() {
const placements = ["bottom", "top", "left", "right"] as const;
const labels = {bottom: "底部", left: "左侧", right: "右侧", top: "顶部"};
return (
{placements.map((p) => (
{labels[p]}
{p === "bottom" && }
悬浮面板:{labels[p]}
面板悬浮于视口边缘内侧,四个角均为圆角。
{p === "top" && }
))}
);
}
```
### 受控状态
组合使用 `isOpen` 和 `onOpenChange`,或用 `defaultOpen` 设置非受控初始状态。受控面板可以省略 `Sheet.Trigger`。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const [isOpen, setIsOpen] = useState(false);
return (
setIsOpen(true)}>
打开面板
setIsOpen(!isOpen)}>
切换
状态:{" "}
{isOpen ? "已打开" : "已关闭"}
受控面板
通过 isOpen 和 onOpenChange{" "}
属性控制面板,由父组件在外部管理状态。
关闭
);
}
```
### 拖动手柄
手柄提供可见的拖动提示,并支持指针拖动。面板完全展开前,触摸滑动也可作用于面板本身,并不限于手柄区域。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
export function HandleOnly() {
return (
拖动手柄
滑动面板
手柄提供可拖动的视觉提示。面板完全展开前,可以在面板任意位置滑动;完全展开后,滑动会滚动内容,而不再移动面板。
完成
);
}
```
### 嵌套面板
将 `Sheet.NestedRoot` 放在另一个面板内。React Aria 管理堆叠和焦点恢复,只有最底层面板使页面背景变暗。
```tsx
"use client";
import {Button, Sheet} from "@heroui/react";
const NESTED_DIALOG_HEIGHT = "h-[320px]";
export function Nested() {
return (
打开父面板
父面板
这是父面板。打开嵌套面板时,父面板会缩小,子面板滑入其上方。
打开嵌套面板
嵌套面板
这是位于父面板上方的嵌套面板。向下拖动即可关闭并返回父面板。
继续深入
第三层
已经到达第三层!下一层打开时,其父面板会缩小,形成堆叠效果。
关闭
返回
);
}
```
### 职业选择器
在初始吸附点为 355px 的面板中组合搜索框和虚拟列表。选择职业后更新所选值并关闭面板。
```tsx
"use client";
import {
Button,
EmptyState,
Label,
ListBox,
ListLayout,
SearchField,
Sheet,
Virtualizer,
} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useMemo, useState} from "react";
const PROFESSIONS = [
"会计师和审计师",
"演员",
"精算师",
"行政服务经理",
"广告和推广经理",
"广告销售代理",
"航空航天工程师",
"农业工程师",
"空中交通管制员",
"飞机机械师和维修技术员",
"救护车司机和随车人员",
"麻醉科医生",
"动物管理人员",
"动画师",
"仲裁员和调解员",
"建筑师(景观和船舶建筑除外)",
"建筑与工程经理",
"档案管理员",
"艺术总监",
"运动防护师",
"大气与空间科学家",
"听力学家",
"汽车车身维修工",
"汽车维修技术员和机械师",
"行李搬运员和酒店行李员",
"面包师",
"理发师",
"调酒师",
"自行车维修工",
"账单与记账文员",
"生物化学家和生物物理学家",
"生物医学工程师",
"簿记、会计和审计文员",
"砖石砌筑工",
"广播技术员",
"预算分析师",
"公交车司机",
"业务运营专员",
"屠宰工和肉类切割工",
"橱柜制作工和木工",
"电视、视频和电影摄影师",
"心血管技术人员",
"货运代理",
"木匠",
"收银员",
"水泥和混凝土修整工",
"厨师长和主厨",
"化学工程师",
"化学家",
"首席执行官",
"儿童、家庭和学校社会工作者",
"脊椎治疗师",
"土木工程师",
"理赔核算员、审查员和调查员",
"临床检验技术人员",
"教练和球探",
"商业飞行员",
"社区卫生工作者",
"薪酬福利经理",
"计算机与信息系统经理",
"计算机硬件工程师",
"计算机网络架构师",
"程序员",
"计算机系统分析师",
"自然资源保护科学家",
"建筑工人",
"建筑项目经理",
"惩教人员和监狱看守",
"成本估算师",
"咨询师",
"法庭记录员和实时字幕员",
"信用分析师",
"策展人",
"客服代表",
"网络安全分析师",
"数据科学家",
"数据库管理员",
"牙科助理",
"口腔卫生师",
"牙医",
"侦探和刑事调查员",
"医学超声诊断技师",
"膳食与营养师",
"宗教活动和教育负责人",
"唱片骑师",
"调度员",
"经济学家",
"编辑",
"教育行政人员",
"电气工程师",
"电工",
"小学教师",
"应急管理负责人",
"急救技术员和护理人员",
"环境工程师",
"环境科学家和专员",
"流行病学家",
"活动策划师",
"运动生理学家",
"时装设计师",
"影视剪辑师",
"金融分析师",
"金融审查员",
"财务经理",
"消防检查员和调查员",
"消防员",
"健身教练和有氧运动指导员",
"空乘人员",
"花艺设计师",
"食品科学家和技术人员",
"法医学技术员",
"筹款专员",
"殡葬服务人员",
"总经理和运营经理",
"遗传咨询师",
"地理学家",
"地球科学家",
"玻璃安装工",
"平面设计师",
"美发师、发型师和美容师",
"健康与安全工程师",
"医疗社会工作者",
"供暖、空调和制冷维修工",
"高中教师",
"历史学家",
"居家健康和个人护理员",
"人力资源经理",
"人力资源专员",
"水文学家",
"工业设计师",
"工业工程师",
"工业机械维修工",
"信息安全分析师",
"保险评估师",
"保险销售代理",
"保险核保员",
"室内设计师",
"口译和笔译人员",
"保洁员",
"珠宝和宝石加工人员",
"新闻工作者",
"法官",
"幼儿园教师",
"景观建筑师",
"律师",
"图书管理员和媒体馆藏专员",
"执业护士",
"信贷员",
"锁匠和保险柜维修工",
"物流专员",
"机器学习工程师",
"机械加工工人",
"家政和客房清洁员",
"管理分析师",
"轮机工程师和船舶设计师",
"市场研究分析师",
"市场营销经理",
"婚姻和家庭治疗师",
"按摩治疗师",
"数学家",
"机械工程师",
"医疗助理",
"病案管理专员",
"医学科学家",
"微生物学家",
"初中教师",
"工业设备安装工",
"采矿和地质工程师",
"音乐总监和作曲家",
"音乐家和歌手",
"网络与计算机系统管理员",
"核工程师",
"麻醉专科护士",
"高级执业护士",
"护理助理",
"职业健康与安全专员",
"作业治疗师",
"配镜师",
"验光师",
"正畸医生",
"律师助理和法律助理",
"停车执法人员",
"病理科医生",
"儿科医生",
"个人理财顾问",
"害虫防治人员",
"药剂师",
"药房技术员",
"采血员",
"摄影师",
"物理治疗助理",
"物理治疗师",
"医师助理",
"内外科医生",
"物理学家",
"工业管道和蒸汽管道安装工",
"水管工",
"足病医生",
"警察",
"政治学家",
"邮递员",
"高等教育教师",
"学前教育教师",
"私家侦探和调查员",
"缓刑监督官",
"制片人和导演",
"产品经理",
"物业经理",
"口腔修复医生",
"精神科技术人员",
"精神科医生",
"心理学家",
"公关经理",
"公关专员",
"采购经理",
"放射治疗师",
"放射技术人员",
"房地产经纪人",
"接待员和咨询文员",
"文体活动工作者",
"娱乐治疗师",
"注册护士",
"康复咨询师",
"记者和通讯员",
"呼吸治疗师",
"餐厅厨师",
"屋顶施工工人",
"销售工程师",
"销售经理",
"校车司机",
"学校心理学家",
"保安",
"钣金工",
"治安官和副治安官",
"护肤专员",
"社会与社区服务经理",
"社会工作者",
"软件开发人员",
"软件质量保证分析师",
"音响工程技术员",
"特殊教育教师",
"言语语言治疗师",
"统计学家",
"建筑钢铁结构工",
"成瘾问题咨询师",
"外科医生",
"调查研究员",
"测量师",
"税务审查和征收人员",
"报税服务人员",
"出租车和专职司机",
"技术文档撰写员",
"电信设备安装工",
"银行柜员",
"培训与发展经理",
"培训与发展专员",
"旅行代理",
"城市与区域规划师",
"用户体验设计师",
"兽医",
"兽医技术人员",
"餐厅服务员",
"水处理设备操作员",
"网页开发人员",
"焊接、切割和钎焊工",
"作家",
"动物学家和野生生物学家",
].map((name, i) => ({id: i, name}));
const professionSheetSnaps = ["355px"];
export function ProfessionsPicker() {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState("");
const [selected, setSelected] = useState(null);
const filtered = useMemo(() => {
if (!search) return PROFESSIONS;
const q = search.toLowerCase();
return PROFESSIONS.filter((p) => p.name.toLowerCase().includes(q));
}, [search]);
return (
{
setIsOpen(open);
if (!open) setSearch("");
}}
>
选择职业
{filtered.length === 0 ? (
未找到职业。
) : (
{
const prof = PROFESSIONS.find((p) => p.id === Number(key));
if (prof) {
setSelected(prof.name);
setIsOpen(false);
}
}}
>
{(item) => (
{item.name}
)}
)}
已选择: {selected}
);
}
```
## 自定义样式
通过各部分的 `className` 或 `@layer components` 中的 `sheet__*` BEM 类自定义样式。保留 React Aria 的滚动定位,不要叠加独立的位移动画。
### Tailwind CSS
通过 `className` 为各部分添加 Tailwind CSS 工具类。
```tsx
打开面板
详情
面板内容
```
### 全局 CSS
使用 `@layer components` 自定义 Sheet 的 BEM 类。
```css
@layer components {
.sheet__dialog {
@apply border border-border;
}
.sheet__header {
@apply text-center;
}
.sheet__body {
@apply px-6 pb-6;
}
}
```
## 样式参考
HeroUI 使用 [BEM](https://getbem.com/) 命名规范,使组件样式易于复用和自定义。
### CSS 类
#### 基础类 \[!toc]
* `.sheet__overlay` - 模态遮罩容器
* `.sheet__backdrop` - 面板后方的背景遮罩
* `.sheet__content` - 控制面板尺寸与堆叠的容器
* `.sheet__dialog` - 对话框表面
* `.sheet__header` - 头部区域
* `.sheet__heading` - 对话框标题
* `.sheet__body` - 完全展开后可滚动的内容区域
* `.sheet__footer` - 底部操作区域
* `.sheet__handle` - 可拖动的手柄区域
* `.sheet__handle-bar` - 默认手柄指示条
* `.sheet__close-trigger` - 关闭按钮
#### 遮罩变体 \[!toc]
* `.sheet__backdrop--opaque` - 暗色遮罩(默认)
* `.sheet__backdrop--blur` - 模糊背景遮罩
* `.sheet__backdrop--transparent` - 透明背景遮罩
嵌套时,仅最底层面板显示背景遮罩。
#### 位置变体 \[!toc]
* `.sheet__content--bottom`, `.sheet__dialog--bottom` - 底部(默认)
* `.sheet__content--top`, `.sheet__dialog--top` - 顶部
* `.sheet__content--left`, `.sheet__dialog--left` - 左侧
* `.sheet__content--right`, `.sheet__dialog--right` - 右侧
### 交互状态
* `[data-focus-visible="true"]` - 关闭按钮的键盘焦点;原生回退选择器为 `:focus-visible:not([data-focused])`
* `:hover`, `[data-hovered="true"]` - 关闭按钮悬停
* `:disabled`, `[aria-disabled="true"]` - 关闭按钮禁用
* `[data-dragging]` - 正在拖动手柄
* `[data-expanded]` - 面板完全展开,启用内容滚动
* `[data-has-descendants]` - 存在已打开的嵌套面板
* `[data-detached]` - 面板与边缘留有间距,并使用圆角
* `[data-snap-points]` - 面板配置了部分展开吸附点
* `[data-entering]`, `[data-exiting]` - 打开与关闭的生命周期状态
* `[data-closed]` - 保留的已关闭遮罩,禁用指针交互
### 数据属性
DOM 部分提供以下 `data-slot`:`sheet-overlay`、`sheet-backdrop`、`sheet-content`、`sheet-dialog`、`sheet-header`、`sheet-heading`、`sheet-body`、`sheet-footer`、`sheet-handle`、`sheet-handle-bar` 和 `sheet-close-trigger`。根组件、Trigger 和 Close 不添加 DOM 元素。
`Sheet.Content` 还提供 `data-placement`、`data-detached` 和 `data-snap-points`。React Aria 在相应部分提供 `data-position`、`data-expanded`、`data-stack-index`、`data-has-descendants`、`data-entering` 和 `data-exiting`。
## API 参考
### Sheet / Sheet.Root / Sheet.NestedRoot
| 属性 | 类型 | 默认值 | 说明 |
| ---------------- | ---------------------------------------- | ---------- | --------------------- |
| `children` | `ReactNode` | — | 组合部分。 |
| `isOpen` | `boolean` | — | 受控打开状态。 |
| `defaultOpen` | `boolean` | `false` | 非受控初始状态。 |
| `onOpenChange` | `(open: boolean) => void` | — | 请求打开或关闭时调用。 |
| `onClose` | `() => void` | — | 请求关闭时调用。 |
| `onAnimationEnd` | `(open: boolean) => void` | — | 进入或退出结束后调用。 |
| `placement` | `"bottom" \| "top" \| "left" \| "right"` | `"bottom"` | 屏幕的物理边缘。 |
| `isDismissable` | `boolean` | `true` | 允许外部交互、Escape 和滑动关闭。 |
| `isDetached` | `boolean` | `false` | 面板与屏幕边缘保持间距。 |
| `snapPoints` | `(number \| string)[]` | — | 部分展开的吸附位置;完整展开位置隐式存在。 |
`Sheet.NestedRoot` 必须在父 Sheet 内使用。根组件的 `isNested` 为内部标记;嵌套时使用 `NestedRoot`。
### Sheet.Trigger
| 属性 | 类型 | 默认值 | 说明 |
| ---------- | -------------- | --- | -------------------------------- |
| `children` | `ReactElement` | — | 一个可按压的子元素,如 Button。直接放在 Sheet 内。 |
### Sheet.Close
| 属性 | 类型 | 默认值 | 说明 |
| ---------- | -------------- | --- | -------------------------------- |
| `children` | `ReactElement` | — | 一个可按压的子元素;其 onPress 回调与关闭操作组合执行。 |
### Sheet.Backdrop
| 属性 | 类型 | 默认值 | 说明 |
| ----------- | ------------------------------------- | ---------- | ---------------- |
| `children` | `ReactNode` | — | 要渲染的内容。 |
| `className` | `string` | — | CSS 类名。 |
| `variant` | `"opaque" \| "blur" \| "transparent"` | `"opaque"` | 遮罩外观。 |
| `isExiting` | `boolean` | — | 关闭后保留内容,直到清除此标记。 |
其他属性继承自 React Aria SheetOverlay,但不包含由根组件控制的状态、位置、吸附点和 `style`。关闭仍会释放模态焦点限制和页面滚动锁定。不再需要保留内容时,请清除 `isExiting`。
### Sheet.Content
| 属性 | 类型 | 默认值 | 说明 |
| ----------- | -------------------------------------------------------- | --- | ------------------------- |
| `children` | `ReactNode \| ((values: SheetRenderProps) => ReactNode)` | — | 内容或接收 React Aria 面板状态的函数。 |
| `className` | `string` | — | CSS 类名。 |
其余属性继承自 React Aria Sheet。HeroUI 管理堆叠动画与过度滚动留白;`stackAnimation` 和 `overscrollPadding` 无法通过此组件覆盖。
### Sheet.Dialog
| 属性 | 类型 | 默认值 | 说明 |
| ------------------ | --------------------------- | ---------- | --------------- |
| `children` | `ReactNode` | — | 要渲染的内容。 |
| `className` | `string` | — | CSS 类名。 |
| `role` | `"dialog" \| "alertdialog"` | `"dialog"` | 对话框无障碍角色。 |
| `aria-label` | `string` | — | 没有可见标题时的无障碍名称。 |
| `aria-labelledby` | `string` | — | 为对话框提供名称的元素 ID。 |
| `aria-describedby` | `string` | — | 描述对话框的元素 ID。 |
其余属性与 ref 继承自 React Aria SheetContent。
### Sheet.Header
| 属性 | 类型 | 默认值 | 说明 |
| ----------- | ----------- | --- | ------- |
| `children` | `ReactNode` | — | 头部内容。 |
| `className` | `string` | — | CSS 类名。 |
支持标准 div 属性、事件处理函数和 ref。
### Sheet.Heading
| 属性 | 类型 | 默认值 | 说明 |
| ----------- | ----------- | --------- | ---------- |
| `children` | `ReactNode` | — | 对话框标题。 |
| `className` | `string` | — | CSS 类名。 |
| `slot` | `string` | `"title"` | 将标题关联到对话框。 |
其余属性与 ref 继承自 React Aria Heading。
### Sheet.Body
| 属性 | 类型 | 默认值 | 说明 |
| ----------- | ----------- | --- | --------- |
| `children` | `ReactNode` | — | 可滚动的主体内容。 |
| `className` | `string` | — | CSS 类名。 |
支持标准 div 属性、事件处理函数和 ref。
### Sheet.Footer
| 属性 | 类型 | 默认值 | 说明 |
| ----------- | ----------- | --- | ------- |
| `children` | `ReactNode` | — | 底部操作。 |
| `className` | `string` | — | CSS 类名。 |
支持标准 div 属性、事件处理函数和 ref。
### Sheet.Handle
| 属性 | 类型 | 默认值 | 说明 |
| ----------- | ----------- | --- | ----------------- |
| `children` | `ReactNode` | — | 自定义手柄;省略时渲染默认指示条。 |
| `className` | `string` | — | CSS 类名。 |
支持标准 div 属性、事件处理函数和 ref。
### Sheet.CloseTrigger
| 属性 | 类型 | 默认值 | 说明 |
| ------------ | --------------------------------------------------------- | --------- | ---------------- |
| `children` | `ReactNode \| ((values: ButtonRenderProps) => ReactNode)` | — | 自定义内容;默认显示关闭图标。 |
| `className` | `string` | — | CSS 类名。 |
| `isDisabled` | `boolean` | `false` | 禁用关闭按钮。 |
| `aria-label` | `string` | `"Close"` | 无障碍名称;按需提供本地化文本。 |
| `onPress` | `(event: PressEvent) => void` | — | 与面板关闭操作一起调用。 |
其余属性与 ref 继承自 CloseButton。
命名导出包括 `SheetRoot`、`SheetTrigger` 等各部分、对应的属性类型、`SheetProps`、`SheetNestedRootProps`、`sheetVariants` 和 `SheetVariants`。组合属性类型使用索引访问,例如 `Sheet["Props"]` 和 `Sheet["ContentProps"]`。
## Sheet 与 Drawer
Sheet 使用 React Aria 原生滚动手势,支持部分吸附位置和嵌套堆叠。Drawer 保留原有 API 和行为;新增 Sheet 不会迁移或弃用 Drawer。
## 无障碍
* **无障碍名称**:使用 `Sheet.Heading`、`aria-label` 或 `aria-labelledby` 为对话框提供名称。
* **焦点管理**:打开时将焦点移入面板并限制在当前模态面板内。关闭后,焦点返回触发器;关闭嵌套面板后返回父面板。
* **键盘**:使用 `Tab` 和 `Shift+Tab` 在控件间移动;允许关闭时,`Escape` 关闭面板。
* **关闭控件**:`Sheet.Close` 和 `Sheet.CloseTrigger` 支持键盘操作,即使 `isDismissable={false}` 也可关闭面板。
* **滚动锁定**:模态面板打开时锁定页面滚动。完全展开后,`Sheet.Body` 可滚动。保留的已关闭内容不会继续锁定页面。
* **拖动手柄**:手柄对辅助技术隐藏,不可通过键盘拖动。请提供可访问的关闭控件,不要仅依赖滑动手势。
## 相关组件
## Related Components
* **Drawer**: Slide-out panel for supplementary content and actions
* **Modal**: Displays content in a modal overlay
* **Button**: Allows a user to perform an action
# Toast 轻提示
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/toast
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(overlays)/toast.mdx
> 向用户展示临时通知与消息,支持自动消失与可定制的放置位置。
## 用法
```tsx
import { Toast, toast } from '@heroui/react';
```
```tsx
"use client";
import {Persons} from "@gravity-ui/icons";
import {Button, toast} from "@heroui/react";
export function Default() {
return (
{
const id = toast("您已被邀请加入团队", {
actionProps: {
children: "忽略",
onPress: () => toast.close(id),
variant: "tertiary",
},
description: "Bob 邀请您加入 HeroUI 团队",
indicator: ,
variant: "default",
});
}}
>
显示 Toast
);
}
```
## 组件结构
```tsx
```
## 示例
### 变体
```tsx
"use client";
import {HardDrive, Persons} from "@gravity-ui/icons";
import {Button, toast} from "@heroui/react";
export function Variants() {
return (
{
const id = toast("您已被邀请加入团队", {
actionProps: {
children: "忽略",
onPress: () => toast.close(id),
variant: "tertiary",
},
description: "Bob 邀请您加入 HeroUI 团队",
indicator: ,
variant: "default",
});
}}
>
默认 Toast
{
const id = toast.info("您还剩 2 个积分", {
actionProps: {children: "升级", onPress: () => toast.close(id)},
description: "升级付费方案以获取更多积分",
});
}}
>
强调 Toast
{
const id = toast.success("您已升级方案", {
actionProps: {
children: "账单",
className: "bg-success text-success-foreground",
onPress: () => toast.close(id),
},
description: "您可以继续使用 HeroUI Chat",
});
}}
>
成功 Toast
{
const id = toast.warning("您的积分已用完", {
actionProps: {
children: "升级",
className: "bg-warning text-warning-foreground",
onPress: () => toast.close(id),
},
description: "升级付费方案以继续使用",
});
}}
>
警告 Toast
{
const id = toast.danger("存储空间已满", {
actionProps: {children: "删除", onPress: () => toast.close(id), variant: "danger"},
description: "删除文件以释放空间。此处增加更多文字以演示较长内容的显示效果",
indicator: ,
});
}}
>
危险 Toast
);
}
```
### 放置位置
```tsx
"use client";
import type {ToastVariants} from "@heroui/react";
import {Button, Toast, ToastQueue} from "@heroui/react";
type Placement = NonNullable;
const placements = ["top start", "top", "top end", "bottom start", "bottom", "bottom end"] as const;
// Create a separate queue for each placement
const placementQueues = Object.fromEntries(
placements.map((p) => [p, new ToastQueue({maxVisibleToasts: 3})]),
) as Record;
export function Placements() {
const showToast = (placement: Placement) => {
placementQueues[placement].add({
description: "活动已创建",
title: "活动已创建",
variant: "default",
});
};
return (
{/* Render a ToastProvider for each placement */}
{placements.map((p) => (
))}
{placements.map((p) => (
showToast(p)}>
{p}
))}
);
}
```
### 展开堆叠
```tsx
"use client";
import {Button, Toast, ToastQueue} from "@heroui/react";
const queue = new ToastQueue();
export function Expanded() {
return (
{
queue.add({
title: "简单消息",
variant: "default",
});
setTimeout(() => {
queue.add({
title: "操作已完成",
variant: "success",
});
}, 400);
setTimeout(() => {
queue.add({
title: "有新更新可用",
variant: "accent",
});
}, 800);
}}
>
显示 3 条 Toast
);
}
```
### 简单 Toast
```tsx
"use client";
import {Button, toast} from "@heroui/react";
export function Simple() {
return (
toast("简单消息")}>
默认
toast.success("操作已完成")}>
成功
toast.info("有新更新可用")}>
信息
toast.warning("请检查您的设置")}>
警告
toast.danger("出了点问题")}>
错误
);
}
```
### 自定义指示器
```tsx
"use client";
import {Star} from "@gravity-ui/icons";
import {Button, toast} from "@heroui/react";
export function CustomIndicator() {
return (
toast("自定义图标指示器", {
indicator: ,
})
}
>
自定义指示器
);
}
```
### 自定义 Toast 渲染
```tsx
"use client";
import type {ToastContentValue} from "@heroui/react";
import {
Button,
Toast,
ToastContent,
ToastDescription,
ToastIndicator,
ToastQueue,
ToastTitle,
} from "@heroui/react";
export function CustomToast() {
const customQueue = new ToastQueue();
return (
{({toast: toastItem}) => {
const content = toastItem.content as ToastContentValue;
return (
{content.title ? (
{content.title}
) : null}
{content.description ? (
{content.description}
) : null}
);
}}
{
customQueue.add({
description: "使用自定义渲染函数",
title: "自定义布局 Toast",
variant: "default",
});
}}
>
自定义 Toast
);
}
```
### Promise 与加载中
```tsx
"use client";
import {Button, toast} from "@heroui/react";
const uploadFile = (): Promise<{filename: string; size: number}> => {
return new Promise<{filename: string; size: number}>((resolve) => {
setTimeout(() => resolve({filename: "document.pdf", size: 1024}), 2000);
});
};
const createEvent = (): Promise => {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("网络错误,请重试。")), 2000);
});
};
const saveData = (): Promise<{count: number}> => {
return new Promise<{count: number}>((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve({count: 42});
} else {
reject(new Error("保存数据失败"));
}
}, 2000);
});
};
const fetchUser = (): Promise<{name: string; email: string}> => {
return new Promise<{name: string; email: string}>((resolve) => {
setTimeout(() => resolve({email: "john@example.com", name: "John Doe"}), 2000);
});
};
export function PromiseDemo() {
return (
{/* Promise API Section */}
使用 toast.promise()
自动处理加载、成功和错误状态
{
toast.promise(uploadFile(), {
error: "上传文件失败",
loading: "正在上传文件…",
success: (data) => `文件 ${data.filename} 已上传(${data.size}KB)`,
});
}}
>
上传文件
{
toast.promise(createEvent(), {
error: (err) => err.message,
loading: "正在创建活动…",
success: "活动已创建",
});
}}
>
创建活动(错误)
{
toast.promise(saveData(), {
error: (err) => err.message,
loading: "正在保存更改…",
success: (data) => `已保存 ${data.count} 项`,
});
}}
>
保存数据(随机)
{
toast.promise(fetchUser(), {
error: "获取用户失败",
loading: "正在加载用户…",
success: (data) => `欢迎回来,${data.name}!`,
});
}}
>
获取用户
{/* Manual Loading Section */}
手动加载状态
使用 isLoading 属性手动控制加载状态
{
const loadingId = toast("正在上传文件…", {
description: "请稍候,正在上传您的文件",
isLoading: true,
timeout: 0,
});
setTimeout(() => {
toast.update(loadingId, "文件已上传", {
description: "您的文件已成功上传",
variant: "success",
});
}, 3000);
}}
>
上传(含加载)
{
const loadingId = toast("正在处理付款…", {
isLoading: true,
timeout: 0,
});
setTimeout(() => {
toast.update(loadingId, "付款已处理", {
description: "您的付款已成功处理",
variant: "success",
});
}, 2500);
}}
>
付款处理
{
const loadingId = toast("正在保存更改…", {
isLoading: true,
timeout: 0,
});
setTimeout(() => {
toast.update(loadingId, "保存失败", {
description: "请重试",
variant: "danger",
});
}, 2000);
}}
>
加载后显示错误
);
}
```
### 回调
```tsx
"use client";
import {Button, toast} from "@heroui/react";
import React from "react";
export function Callbacks() {
const [closedHistory, setClosedHistory] = React.useState>(
[],
);
const addToHistory = (message: string) => {
const time = new Date().toLocaleTimeString();
setClosedHistory((prev) => [{message, time}, ...prev].slice(0, 5));
};
return (
{/* Toast Buttons */}
toast("文件已保存", {
onClose: () => {
addToHistory("文件已保存(3 秒后关闭)");
},
timeout: 3000,
})
}
>
自定义超时(3 秒)
toast("更改已保存", {
onClose: () => {
addToHistory("更改已保存(10 秒后关闭)");
},
timeout: 10000,
})
}
>
自定义超时(10 秒)
toast.success("活动已创建", {
onClose: () => {
addToHistory("活动已创建(默认超时后关闭)");
},
})
}
>
使用 onClose 回调
toast("重要通知", {
description: "此 Toast 将保持显示直至关闭",
onClose: () => {
addToHistory("重要通知(手动关闭)");
},
timeout: 0,
})
}
>
持久显示 Toast
{/* 关闭历史 Panel */}
关闭历史
{closedHistory.length > 0 && (
setClosedHistory([])}
>
清空
)}
{closedHistory.length === 0 ? (
尚无已关闭的 Toast。请尝试关闭上方的 Toast!
) : (
closedHistory.map((item, index) => (
{item.message}
({item.time})
))
)}
);
}
```
### 自定义队列
```tsx
"use client";
import {Button, Toast, ToastQueue} from "@heroui/react";
export function CustomQueue() {
const notificationQueue = new ToastQueue({maxVisibleToasts: 2});
const errorQueue = new ToastQueue({maxVisibleToasts: 3});
const successQueue = new ToastQueue({maxVisibleToasts: 1});
return (
{/* Notification Queue */}
{
notificationQueue.add({
description: "您有一条新消息",
title: "新通知",
variant: "default",
});
}}
>
添加通知(最多 2 条)
{/* Error Queue */}
{
errorQueue.add({
description: "保存更改失败",
title: "发生错误",
variant: "danger",
});
}}
>
添加错误(最多 3 条)
{/* Success Queue */}
{
successQueue.add({
description: `操作 ${Date.now()}`,
title: "成功!",
variant: "success",
});
}}
>
添加成功(最多 1 条)
);
}
```
### 配置
在应用根节点渲染 Provider。
```tsx
import { Toast, Button, toast } from '@heroui/react';
function App() {
return (
toast("Simple message")}>
Show toast
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import type {ToastContentValue} from "@heroui/react";
import {
Button,
Toast,
ToastContent,
ToastDescription,
ToastIndicator,
ToastQueue,
ToastTitle,
} from "@heroui/react";
export function CustomStyles() {
const queue = new ToastQueue();
return (
{({toast: toastItem}) => {
const content = toastItem.content as ToastContentValue;
return (
{content.title ? (
{content.title}
) : null}
{content.description ? (
{content.description}
) : null}
);
}}
{
queue.add({
description: "草稿已同步",
title: "已保存",
variant: "default",
});
}}
>
显示提示
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.toast {
@apply rounded-xl shadow-lg;
}
.toast__content {
@apply gap-2;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Toast 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/toast.css)):
#### 基础类 \[!toc]
* `.toast` — Toast 根容器
* `.toast-region` — Toast 区域容器
* `.toast__content` — 包裹标题与说明的内容容器
* `.toast__indicator` — 图标/指示器容器
* `.toast__title` — Toast 标题文本
* `.toast__description` — Toast 说明文本
* `.toast__action` — 操作按钮容器
* `.toast__close-button` — 关闭按钮容器
#### 变体类 \[!toc]
* `.toast--default` — 默认灰色变体
* `.toast--accent` — 强调蓝色变体
* `.toast--success` — 成功绿色变体
* `.toast--warning` — 警告黄/橙色变体
* `.toast--danger` — 危险红色变体
### 交互状态
组件支持多种状态:
* **最前**:`[data-frontmost]` — 应用于堆叠中最上层可见的 Toast
* **索引**:`[data-index]` — 基于 Toast 在堆叠中的位置
* **放置**:`[data-placement="*"]` — 基于 Toast 区域的放置位置
* **进入中**:`[data-entering]` — Toast 首次绘制时应用
* **退出中**:`[data-exiting]` — 关闭中的 Toast 播放退出过渡时应用
* **展开**:`[data-expanded]` — 堆叠展开时应用于每个 Toast
* **隐藏**:`[data-hidden]` — 应用于超出 `maxVisibleToasts` 的 Toast
## API 参考
### Toast.Provider
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | --------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `placement` | `"top start" \| "top" \| "top end" \| "bottom start" \| "bottom" \| "bottom end"` | `"bottom"` | Toast 区域的放置位置 |
| `gap` | `number` | `12` | Toast 之间的间距(像素) |
| `isExpanded` | `boolean` | `false` | 强制堆叠保持展开布局(不会暂停计时器) |
| `maxVisibleToasts` | `number` | `3` | 同时最多显示的 Toast 数量 |
| `hotkey` | `string[]` | `["altKey", "KeyT"]` | 将焦点移入 Toast 区域并展开堆叠的快捷键。修饰键使用 `KeyboardEvent` 布尔属性名(如 `"altKey"`),其他按键使用 `event.code`(如 `"KeyT"`)。未列出的修饰键必须处于松开状态,因此 `Alt`+`T` 不会在 `Ctrl`+`Alt`+`T` 时触发。传 `[]` 可禁用 |
| `scaleFactor` | `number` | `0.05` | 堆叠 Toast 的缩放系数(0–1) |
| `width` | `number \| string` | `460` | Toast 宽度(像素或 CSS 值) |
| `queue` | `ToastQueue` | - | 自定义 Toast 队列实例 |
| `children` | `ReactNode \| ((props: {toast: QueuedToast}) => ReactNode)` | - | 自定义渲染函数或子节点 |
| `className` | `string` | - | 附加的 CSS 类 |
### Toast
| Prop | 类型 | 默认值 | 描述 |
| ------------- | ------------------------------------------------------------- | ----------- | --------------------------------------- |
| `toast` | `QueuedToast` | - | 来自队列的 Toast 数据(必填) |
| `variant` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Toast 的视觉变体 |
| `placement` | `ToastVariants["placement"]` | - | 放置位置(继承自 Provider) |
| `scaleFactor` | `number` | - | 缩放系数(继承自 Provider) |
| `className` | `string` | - | 附加的 CSS 类 |
| `children` | `ReactNode` | - | Toast 内容(ToastContent、ToastIndicator 等) |
### Toast.Content
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------------------------------- |
| `children` | `ReactNode` | - | 内容(通常为 ToastTitle 与 ToastDescription) |
| `className` | `string` | - | 附加的 CSS 类 |
### Toast.Indicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------- | --- | ----------------- |
| `variant` | `ToastVariants["variant"]` | - | 默认图标的变体 |
| `children` | `ReactNode` | - | 自定义指示图标(默认使用变体图标) |
| `className` | `string` | - | 附加的 CSS 类 |
### Toast.Title
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------- |
| `children` | `ReactNode` | - | 标题文本 |
| `className` | `string` | - | 附加的 CSS 类 |
### Toast.Description
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | --------- |
| `children` | `ReactNode` | - | 说明文本 |
| `className` | `string` | - | 附加的 CSS 类 |
### Toast.ActionButton
| Prop | 类型 | 默认值 | 描述 |
| ------------------ | ----------- | --- | --------------------- |
| `children` | `ReactNode` | - | 操作按钮内容 |
| `className` | `string` | - | 附加的 CSS 类 |
| All `Button` props | - | - | 接受 Button 组件的全部 props |
### Toast.CloseButton
| Prop | 类型 | 默认值 | 描述 |
| ----------------------- | -------- | --- | -------------------------- |
| `className` | `string` | - | 附加的 CSS 类 |
| All `CloseButton` props | - | - | 接受 CloseButton 组件的全部 props |
### ToastQueue
`ToastQueue` 用于管理 `` 的状态。状态存放在 React 之外,因此你可以在应用的任意位置触发 Toast。
#### 构造选项
| Option | 类型 | 默认值 | 描述 |
| ------------------ | -------------------------- | ----- | --------------------------------------------------------------------------- |
| `exitDuration` | `number` | `300` | 关闭中的 Toast 为播放退出动画而保持挂载的时长(0 表示立即移除) |
| `maxVisibleToasts` | `number` | `3` | 同时最多显示的 Toast 数量(仅视觉) |
| `wrapUpdate` | `(fn: () => void) => void` | - | 包裹状态更新的函数(例如 `document.startViewTransition`);默认直接应用更新并由 CSS transition 驱动动画 |
#### 方法
| Method | 参数 | 返回值 | 描述 |
| ----------- | ------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------- |
| `add` | `(content: T, options?: ToastOptions)` | `string` | 将 Toast 加入队列,返回 Toast key |
| `update` | `(key: string, content: T, options?: {timeout?: number; onClose?: () => void})` | `boolean` | 原地更新 Toast,保持其在堆叠中的位置;`timeout` 重新开始倒计时(0 表示保持打开),省略则沿用当前倒计时。Toast 已不存在时返回 `false` |
| `close` | `(key: string)` | `void` | 按 key 关闭 Toast |
| `pauseAll` | `()` | `void` | 暂停所有 Toast 计时器 |
| `resumeAll` | `()` | `void` | 恢复所有 Toast 计时器 |
| `clear` | `()` | `void` | 关闭所有 Toast(每个 Toast 播放退出动画并触发各自的 `onClose`) |
| `subscribe` | `(fn: () => void)` | `() => void` | 订阅队列变化,返回取消订阅函数 |
### toast 函数
默认 `toast` 函数提供便捷方法用于显示 Toast:
```tsx
import { toast } from '@heroui/react';
// 基础 toast(默认约 4 秒后自动消失)
toast("Event has been created");
// 变体方法(默认同样约 4 秒后自动消失)
toast.success("File saved");
toast.info("New update available");
toast.warning("Please check your settings");
toast.danger("Something went wrong");
// 传入 options。利用返回的 id,操作按钮可以关闭它所属的 Toast。
const eventId = toast("Event has been created", {
description: "Your event has been scheduled for tomorrow",
variant: "default",
timeout: 5000, // 自定义超时:5 秒
onClose: () => console.log("Closed"),
actionProps: {
children: "View",
onPress: () => toast.close(eventId),
},
indicator: ,
});
// 原地更新已有 Toast(保持其在堆叠中的位置)。
// 省略的选项会被继承,因此若原 Toast 是持久的,需要显式传入 `timeout` 才会开始倒计时。
const id = toast("Saving…", { timeout: 0 });
toast.update(id, "Saved", { variant: "success", timeout: 4000 });
// Promise 支持(自动显示加载指示)。Promise 结束时加载 Toast 会原地更新 —
// 同一个 Toast、同一个堆叠位置;自动关闭倒计时从这一刻开始。
toast.promise(
uploadFile(),
{
loading: "Uploading file...",
success: (data) => `File ${data.filename} uploaded`,
error: "Failed to upload file",
}
);
// 手动加载状态(持久 toast:不自动消失)
const loadingId = toast("Creating event...", {
isLoading: true,
timeout: 0, // 持久 toast:不自动消失
});
// 随后原地更新,并开始自动关闭倒计时
toast.update(loadingId, "Event created", { variant: "success", timeout: 4000 });
// 队列方法
toast.close(key);
toast.clear();
toast.pauseAll();
toast.resumeAll();
```
#### toast Options
| Option | 类型 | 默认值 | 描述 |
| ------------- | ------------------------------------------------------------- | ----------- | ------------------------------------------------- |
| `title` | `ReactNode` | - | Toast 标题(变体方法的第一个参数) |
| `description` | `ReactNode` | - | 可选说明文本 |
| `variant` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | 视觉变体 |
| `indicator` | `ReactNode` | - | 自定义指示图标(`null` 可隐藏) |
| `actionProps` | `ButtonProps` | - | 操作按钮 props |
| `isLoading` | `boolean` | `false` | 使用加载指示替代指示器 |
| `timeout` | `number` | `4000` | 自动消失超时(毫秒)。默认 4000ms(4 秒)。设为 `0` 表示持久 Toast,不自动消失 |
| `onClose` | `() => void` | - | Toast 被关闭时触发(此时退出动画开始播放) |
#### toast.promise Options
| Option | 类型 | 默认值 | 描述 |
| --------- | -------------------------------------------- | --- | ---------------------- |
| `loading` | `ReactNode` | - | Promise pending 时显示的消息 |
| `success` | `ReactNode \| ((data: T) => ReactNode)` | - | 成功时显示的消息(可为函数) |
| `error` | `ReactNode \| ((error: Error) => ReactNode)` | - | 失败时显示的消息(可为函数) |
## 无障碍
每个 Toast 实现 [WAI-ARIA AlertDialog 模式](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/),并分组渲染在带标签的地标区域中:
* **地标区域**:Toast 渲染在可通过 `F6` 到达的带标签地标区域中;`Alt` + `T` 可将焦点直接移入。快捷键可通过 `hotkey` prop 配置,传空数组可禁用;区域标签可通过 `aria-label` 覆盖
* **键盘**:`Tab` 可到达可见 Toast 的控件,`Escape` 折叠展开的堆叠;关闭聚焦的 Toast 时焦点会移至最近的剩余 Toast
* **屏幕阅读器**:每个 Toast 使用 `role="alertdialog"` 并关联标题与描述;新 Toast 会自动播报
* **计时器**:悬停或聚焦堆叠时、以及页面处于后台标签页时,自动关闭计时会暂停
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Alert**: Display important messages and notifications
* **CloseButton**: Button for dismissing overlays
# Tooltip 工具提示
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/tooltip
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(overlays)/tooltip.mdx
> 当用户悬停或聚焦某个元素时,展示提示性文本。
## 用法
```tsx
import { Tooltip } from '@heroui/react';
```
```tsx
import {CircleInfo} from "@gravity-ui/icons";
import {Button, Tooltip} from "@heroui/react";
export function TooltipBasic() {
return (
);
}
```
## 组件结构
```tsx
import { Tooltip, Button } from '@heroui/react';
export default () => (
Hover for tooltip
关于此元素的有用信息
)
```
## 示例
### 位置
```tsx
import {Button, Tooltip} from "@heroui/react";
export function TooltipPlacement() {
return (
Top
顶部位置
Left
左侧位置
悬停按钮
Right
右侧位置
Bottom
底部位置
);
}
```
### 带箭头
```tsx
import {Button, Tooltip} from "@heroui/react";
export function TooltipWithArrow() {
return (
带箭头
带箭头指示器的工具提示
自定义偏移
与触发器的自定义偏移
);
}
```
### 自定义触发
```tsx
import {CircleCheckFill, CircleQuestion} from "@gravity-ui/icons";
import {Avatar, Chip, Tooltip} from "@heroui/react";
export function TooltipCustomTrigger() {
return (
JD
Jane Doe
jane@example.com
活跃
Jane 当前在线
帮助信息
这是包含有关此功能更详细信息的实用工具提示。
);
}
```
### 渲染函数
```tsx
"use client";
import {CircleInfo} from "@gravity-ui/icons";
import {Button, Tooltip} from "@heroui/react";
export function RenderFunction() {
return (
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Button, Tooltip} from "@heroui/react";
export function CustomStyles() {
return (
分享链接
已复制到剪贴板
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.tooltip {
@apply rounded-xl shadow-lg;
}
.tooltip__trigger {
@apply cursor-help;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### 全局延迟配置
你可以通过定义 CSS 变量,为应用中所有 Tooltip 设置默认的显示与隐藏延迟:
```css
/* 在你的全局 CSS 文件中 */
:root {
--tooltip-delay: 1500ms;
--tooltip-close-delay: 500ms;
}
/* 也可以为浅色/深色主题设置不同的值 */
.light, [data-theme="light"] {
--tooltip-delay: 1200ms;
}
.dark, [data-theme="dark"] {
--tooltip-close-delay: 300ms;
}
```
值支持 `ms`、`s` 等 CSS 时间单位。在单个 Tooltip 上指定 `delay` 或 `closeDelay` 时,会覆盖这些全局设置。
### CSS 类
Tooltip 使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tooltip.css)):
#### 基础类 \[!toc]
* `.tooltip` - 带动画的基础 Tooltip 样式
* `.tooltip__trigger` - 触发元素样式
### 交互状态
组件支持以下动画相关状态:
* **进入**:`[data-entering]` — Tooltip 出现过程中应用
* **离开**:`[data-exiting]` — Tooltip 消失过程中应用
* **位置**:`[data-placement="*"]` — 根据 Tooltip 位置应用
## API 参考
### Tooltip
| Prop | 类型 | 默认值 | 描述 |
| --------------------- | -------------------- | --------------- | ---------------------------------------------------------- |
| `children` | `React.ReactNode` | - | 触发元素与内容 |
| `delay` | `number` | `1500` 或 CSS 变量 | 显示 Tooltip 前的延迟(毫秒);可通过 `--tooltip-delay` CSS 变量全局配置 |
| `closeDelay` | `number` | `500` 或 CSS 变量 | 隐藏 Tooltip 前的延迟(毫秒);可通过 `--tooltip-close-delay` CSS 变量全局配置 |
| `trigger` | `"hover" \| "focus"` | `"hover"` | Tooltip 的触发方式 |
| `isDisabled` | `boolean` | `false` | 是否禁用 Tooltip |
| `shouldSkipAnimation` | `boolean` | `false` | 在多个 Tooltip 之间快速切换时,是否跳过进入与退出动画 |
### Tooltip.Content
| Prop | 类型 | 默认值 | 描述 |
| ----------- | -------------------------------------------------------------------------- | ------------ | ---------------------- |
| `children` | `React.ReactNode` | - | 在 Tooltip 中展示的内容 |
| `showArrow` | `boolean` | `false` | 是否显示箭头指示器 |
| `offset` | `number` | `3`(带箭头时为 7) | 与触发元素的距离 |
| `placement` | `"top" \| "bottom" \| "left" \| "right"` (及变体) | `"top"` | Tooltip 的位置 |
| `className` | `string` | - | 额外的 CSS 类名 |
| `render` | `DOMRenderFunction` | - | 通过自定义渲染函数覆盖默认的 DOM 元素。 |
### Tooltip.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | -------------- |
| `children` | `React.ReactNode` | - | 触发 Tooltip 的元素 |
| `className` | `string` | - | 额外的 CSS 类名 |
### Tooltip.Arrow
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------- | --- | ---------------------- |
| `children` | `React.ReactNode` | - | 自定义箭头元素 |
| `className` | `string` | - | 额外的 CSS 类名 |
| `render` | `DOMRenderFunction` | - | 通过自定义渲染函数覆盖默认的 DOM 元素。 |
## 相关组件
## Related Components
* **Button**: Allows a user to perform an action
* **Popover**: Displays content in context with a trigger
# Autocomplete 自动完成
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/autocomplete
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(pickers)/autocomplete.mdx
> 结合选择与过滤,让用户从选项列表中搜索并选择
## 用法
```tsx
import { Autocomplete, useFilter } from "@heroui/react";
```
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export default function Default() {
const {contains} = useFilter({sensitivity: "base"});
const [selectedKeys, setSelectedKeys] = useState([]);
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
计划前往的州
{({defaultChildren, isPlaceholder, state}: any) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item: any) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey: Key) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
未找到结果 }>
{items.map((item) => (
{item.name}
))}
);
}
```
## 组件结构
```tsx
import {Autocomplete, Label, Description, SearchField, ListBox} from "@heroui/react";
export default () => (
);
```
## 示例
### 变体
Autocomplete 组件支持两种视觉变体:
* **`primary`**(默认)- 标准样式带阴影,适用于大多数场景
* **`secondary`** - 低强调变体无阴影,适用于 Surface 组件内
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function Variants() {
const [selectedKey1, setSelectedKey1] = useState(null);
const [selectedKey2, setSelectedKey2] = useState(null);
const [selectedKeys1, setSelectedKeys1] = useState([]);
const [selectedKeys2, setSelectedKeys2] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "option1", name: "选项 1"},
{id: "option2", name: "选项 2"},
{id: "option3", name: "选项 3"},
{id: "option4", name: "选项 4"},
];
const onRemoveTags1 = (keys: Set) => {
setSelectedKeys1((prev) => prev.filter((key) => !keys.has(key)));
};
const onRemoveTags2 = (keys: Set) => {
setSelectedKeys2((prev) => prev.filter((key) => !keys.has(key)));
};
return (
单选变体
主色(primary)变体
未找到结果 }>
{items.map((item) => (
{item.name}
))}
次色(secondary)变体
未找到结果 }>
{items.map((item) => (
{item.name}
))}
多选变体
setSelectedKeys1(keys as Key[])}
>
主色(primary)变体
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
未找到结果 }>
{items.map((item) => (
{item.name}
))}
setSelectedKeys2(keys as Key[])}
>
次色(secondary)变体
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
未找到结果 }>
{items.map((item) => (
{item.name}
))}
);
}
```
### 宽度充满
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Surface,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function FullWidth() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
州
未找到结果 }>
{items.map((item) => (
{item.name}
))}
);
}
```
### 带描述
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function WithDescription() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
州
未找到结果 }>
{items.map((item) => (
{item.name}
))}
请选择你的居住州
);
}
```
### 必填
```tsx
"use client";
import {
Autocomplete,
Button,
EmptyState,
FieldError,
Form,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
export function Required() {
const onSubmit = (e: React.SyntheticEvent) => {
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("表单提交成功!");
};
const {contains} = useFilter({sensitivity: "base"});
const states = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
const countries = [
{id: "usa", name: "United States"},
{id: "canada", name: "Canada"},
{id: "mexico", name: "Mexico"},
{id: "uk", name: "United Kingdom"},
{id: "france", name: "France"},
{id: "germany", name: "Germany"},
];
return (
州
未找到结果 }>
{states.map((state) => (
{state.name}
))}
国家
未找到结果 }>
{countries.map((country) => (
{country.name}
))}
提交
);
}
```
### 禁用
```tsx
"use client";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
export function Disabled() {
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
const countries = [
{id: "argentina", name: "Argentina"},
{id: "venezuela", name: "Venezuela"},
{id: "japan", name: "Japan"},
{id: "france", name: "France"},
{id: "italy", name: "Italy"},
{id: "spain", name: "Spain"},
];
return (
州
未找到结果 }>
{items.map((item) => (
{item.name}
))}
计划前往的国家
未找到结果 }>
{countries.map((country) => (
{country.name}
))}
);
}
```
### 含禁用选项
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {useState} from "react";
export function WithDisabledOptions() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
return (
动物
未找到结果 }>
狗
猫
鸟
袋鼠
象
老虎
);
}
```
### 允许空选项
`allowsEmptyCollection` 属性允许集合为空时仍可使用自动完成,适用于列表初始为空或全部被过滤掉的场景。
```tsx
"use client";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
export function AllowsEmptyCollection() {
const {contains} = useFilter({sensitivity: "base"});
return (
州
未找到结果 } />
);
}
```
### 分组选项
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Header,
Label,
ListBox,
SearchField,
Separator,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function WithSections() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
return (
国家
未找到结果 }>
美国
加拿大
墨西哥
英国
法国
德国
西班牙
意大利
日本
中国
印度
韩国
);
}
```
### 多选
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function MultipleSelect() {
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "florida", name: "Florida"},
{id: "new-york", name: "New York"},
{id: "illinois", name: "Illinois"},
{id: "pennsylvania", name: "Pennsylvania"},
];
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
州
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const item = items.find((s) => s.id === selectedItemKey);
if (!item) return null;
return (
{item.name}
);
})}
);
}}
未找到结果 }>
{items.map((item) => (
{item.name}
))}
);
}
```
在多选模式下,直接使用 ` ` 会将每个选中项在 `ListBox.Item` 中的 children
复制到触发器中,并以符合当前语言环境的分隔符连接。仅包含文本的选项会显示为纯文本,但复选框、头像等可见内容会随每个选中项各出现一次;函数形式的
children 也始终以 `isSelected: false` 调用。
如需以纯文本显示选中值,请使用 `selectedText` 渲染属性,它会将每个选中项的 `textValue` 连接为一个字符串:
```tsx
{({isPlaceholder, defaultChildren, selectedText}) =>
isPlaceholder ? defaultChildren : selectedText
}
```
如需可移除标签等更丰富的布局,请参考上方示例,基于 `state.selectedItems` 自行渲染内容。
### 受控组件
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const states = [
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "florida", name: "Florida"},
{id: "new-york", name: "New York"},
{id: "illinois", name: "Illinois"},
{id: "pennsylvania", name: "Pennsylvania"},
];
const [state, setState] = useState("california");
const {contains} = useFilter({sensitivity: "base"});
const selectedState = states.find((s) => s.id === state);
return (
州(受控)
未找到结果 }>
{states.map((state) => (
{state.name}
))}
已选:{selectedState?.name || "无"}
);
}
```
### 受控多选
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {useState} from "react";
export function ControlledMultiple() {
const [selected, setSelected] = useState(["california", "texas"]);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "florida", name: "Florida"},
{id: "new-york", name: "New York"},
{id: "illinois", name: "Illinois"},
{id: "pennsylvania", name: "Pennsylvania"},
];
return (
setSelected(keys as Key[])}
>
州
未找到结果 }>
{items.map((item) => (
{item.name}
))}
已选:{selected.length > 0 ? selected.join(", ") : "无"}
);
}
```
### 受控展开状态
```tsx
"use client";
import {
Autocomplete,
Button,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function ControlledOpenState() {
const [isOpen, setIsOpen] = useState(false);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
州
未找到结果 }>
{items.map((item) => (
{item.name}
))}
setIsOpen(!isOpen)}>{isOpen ? "关闭" : "打开"} 自动完成
自动完成处于{isOpen ? "打开" : "关闭"}状态
);
}
```
### 异步搜索
```tsx
"use client";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, Spinner} from "@heroui/react";
import {useAsyncList} from "@react-stately/data";
import {cn} from "tailwind-variants";
interface Character {
name: string;
}
export function AsynchronousFiltering() {
const list = useAsyncList({
async load({filterText, signal}) {
const res = await fetch(`https://swapi.py4e.com/api/people/?search=${filterText}`, {
signal,
});
const json = await res.json();
return {
items: json.results,
};
},
});
return (
搜索《星球大战》角色
未找到结果 }
>
{(item: Character) => (
{item.name}
)}
);
}
```
### 自定义指示器
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Autocomplete, EmptyState, Label, ListBox, SearchField, useFilter} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";
export function CustomIndicator() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
州
未找到结果 }>
{items.map((item) => (
{item.name}
))}
);
}
```
### 自定义展示值
可使用 render props 自定义显示值:
```tsx
"use client";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
export function CustomValue() {
const {contains} = useFilter({sensitivity: "base"});
const currencies = [
{code: "USD", id: "usd", name: "美元", symbol: "$"},
{code: "EUR", id: "eur", name: "欧元", symbol: "€"},
{code: "GBP", id: "gbp", name: "英镑", symbol: "£"},
{code: "JPY", id: "jpy", name: "日元", symbol: "¥"},
{code: "CHF", id: "chf", name: "瑞士法郎", symbol: "₣"},
];
return (
货币
{({defaultChildren, isPlaceholder, state}) => {
const selectedCurrency = currencies.find(
(currency) => currency.id === state.selectedItems[0]?.key,
);
if (isPlaceholder || !selectedCurrency) {
return defaultChildren;
}
return (
{selectedCurrency.symbol}
{selectedCurrency.code}
{selectedCurrency.name}
);
}}
未找到结果 }>
{currencies.map((currency) => (
{currency.code}
{currency.name}
))}
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 组件内使用时,请使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Surface,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function OnSurface() {
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
const items = [
{id: "florida", name: "Florida"},
{id: "delaware", name: "Delaware"},
{id: "california", name: "California"},
{id: "texas", name: "Texas"},
{id: "new-york", name: "New York"},
{id: "washington", name: "Washington"},
];
return (
州
未找到结果 }>
{items.map((item) => (
{item.name}
))}
);
}
```
### 虚拟滚动
Autocomplete 通过 [Virtualizer](https://react-aria.adobe.com/Virtualizer) 支持虚拟化,仅渲染视口内可见行以高效处理大数据集。
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
ListLayout,
SearchField,
Virtualizer,
useFilter,
} from "@heroui/react";
import {useMemo, useState} from "react";
interface User {
email: string;
id: number;
name: string;
}
function generateUsers(n: number): User[] {
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",
];
const users: User[] = [];
for (let i = 0; i < n; 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,
});
}
return users;
}
export function Virtualization() {
const [selectedKey, setSelectedKey] = useState(null);
const [searchQuery, setSearchQuery] = useState("");
const {contains} = useFilter({sensitivity: "base"});
const allUsers = useMemo(() => generateUsers(1000), []);
const filteredUsers = useMemo(() => {
if (!searchQuery) return allUsers;
return allUsers.filter(
(user) => contains(user.name, searchQuery) || contains(user.email, searchQuery),
);
}, [allUsers, contains, searchQuery]);
return (
用户
未找到结果 }
>
{(user) => (
{user.name}
{user.email}
)}
);
}
```
## 高级示例
### 用户选择
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Avatar,
AvatarFallback,
AvatarImage,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function UserSelection() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
const [selectedKey, setSelectedKey] = useState(null);
const {contains} = useFilter({sensitivity: "base"});
return (
用户
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItems = state.selectedItems;
if (selectedItems.length > 1) {
return `已选择 ${selectedItems.length} 位用户`;
}
const selectedItem = users.find((user) => user.id === selectedItems[0]?.key);
if (!selectedItem) {
return defaultChildren;
}
return (
{selectedItem.fallback}
{selectedItem.name}
);
}}
未找到结果 }>
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### 用户多选
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Avatar,
AvatarFallback,
AvatarImage,
Description,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function UserSelectionMultiple() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
用户
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const selectedItem = users.find((user) => user.id === selectedItemKey);
if (!selectedItem) {
return null;
}
return (
{selectedItem.fallback}
{selectedItem.name}
);
})}
);
}}
未找到结果 }>
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### 地点搜索
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
useFilter,
} from "@heroui/react";
import {useState} from "react";
interface City {
name: string;
country: string;
}
export function LocationSearch() {
const allCities: City[] = [
{country: "美国", name: "纽约"},
{country: "美国", name: "洛杉矶"},
{country: "美国", name: "芝加哥"},
{country: "英国", name: "伦敦"},
{country: "法国", name: "巴黎"},
{country: "日本", name: "东京"},
{country: "澳大利亚", name: "悉尼"},
{country: "加拿大", name: "多伦多"},
{country: "德国", name: "柏林"},
{country: "西班牙", name: "马德里"},
];
const [selectedKey, setSelectedKey] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const {contains} = useFilter({sensitivity: "base"});
// Simulate async filtering
const customFilter = (text: string, inputValue: string) => {
if (!inputValue) return true;
setIsLoading(true);
setTimeout(() => setIsLoading(false), 300);
return contains(text, inputValue);
};
return (
城市
{isLoading ? "搜索中…" : "未找到城市"} }
>
{allCities.map((city) => (
{city.name}
{city.country}
))}
);
}
```
### 标签组选择
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function TagGroupSelection() {
const tags = [
{id: "react", name: "React"},
{id: "typescript", name: "TypeScript"},
{id: "javascript", name: "JavaScript"},
{id: "nodejs", name: "Node.js"},
{id: "python", name: "Python"},
{id: "vue", name: "Vue"},
{id: "angular", name: "Angular"},
{id: "nextjs", name: "Next.js"},
];
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
标签
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const tag = tags.find((t) => t.id === selectedItemKey);
if (!tag) return null;
return (
{tag.name}
);
})}
);
}}
未找到标签 }>
{tags.map((tag) => (
{tag.name}
))}
);
}
```
### 邮件收件人
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
export function EmailRecipients() {
const emails = [
{email: "alice@example.com", id: "alice@example.com", name: "Alice Johnson"},
{email: "bob@example.com", id: "bob@example.com", name: "Bob Smith"},
{email: "charlie@example.com", id: "charlie@example.com", name: "Charlie Brown"},
{email: "diana@example.com", id: "diana@example.com", name: "Diana Prince"},
{email: "eve@example.com", id: "eve@example.com", name: "Eve Wilson"},
];
const [selectedKeys, setSelectedKeys] = useState([]);
const {contains} = useFilter({sensitivity: "base"});
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys(keys as Key[])}
>
收件人
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const email = emails.find((e) => e.id === selectedItemKey);
if (!email) return null;
return (
{email.email}
);
})}
);
}}
未找到收件人 }>
{emails.map((email) => (
{email.name}
{email.email}
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
"use client";
import type {Key} from "@heroui/react";
import {
Autocomplete,
Description,
EmptyState,
Label,
ListBox,
SearchField,
Tag,
TagGroup,
useFilter,
} from "@heroui/react";
import {useState} from "react";
const TEAMMATES = [
{id: "sarah", name: "Sarah Chen", role: "产品设计"},
{id: "marcus", name: "Marcus Lee", role: "工程"},
{id: "priya", name: "Priya Patel", role: "数据科学"},
{id: "jordan", name: "Jordan Kim", role: "客户成功"},
{id: "alex", name: "Alex Rivera", role: "市场营销"},
] as const;
export function CustomStyles() {
const {contains} = useFilter({sensitivity: "base"});
const [selectedKeys, setSelectedKeys] = useState([]);
const onRemoveTags = (keys: Set) => {
setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)));
};
return (
setSelectedKeys((keys ?? []) as Key[])}
>
负责人
任务更新时将通知以下人员。
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItemsKeys = state.selectedItems.map((item) => item.key);
return (
{selectedItemsKeys.map((selectedItemKey) => {
const teammate = TEAMMATES.find((member) => member.id === selectedItemKey);
if (!teammate) return null;
return (
{teammate.name}
);
})}
);
}}
未找到团队成员 }
>
{TEAMMATES.map((teammate) => (
{teammate.name}
{teammate.role}
))}
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Autocomplete 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.autocomplete {
@apply flex flex-col gap-1;
}
.autocomplete__trigger {
@apply rounded-lg border border-border bg-surface p-2;
}
.autocomplete__value {
@apply text-current;
}
.autocomplete__clear-button {
@apply text-muted hover:text-foreground;
}
.autocomplete__indicator {
@apply text-muted;
}
.autocomplete__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Autocomplete 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/autocomplete.css)):
#### 基础类 \[!toc]
* `.autocomplete` - 自动完成根容器
* `.autocomplete__trigger` - 触发自动完成的按钮
* `.autocomplete__value` - 显示的值或占位符
* `.autocomplete__clear-button` - 清除已选值的按钮
* `.autocomplete__indicator` - 下拉指示图标
* `.autocomplete__popover` - 弹出层容器
* `.autocomplete__filter` - 过滤包装器
#### 变体类 \[!toc]
* `.autocomplete--primary` - 带阴影的主变体(默认)
* `.autocomplete--secondary` - 无阴影的次变体,适用于 Surface 内
#### 状态类 \[!toc]
* `.autocomplete[data-invalid="true"]` - 无效状态
* `.autocomplete__trigger[data-focus-visible="true"]` - 触发器聚焦状态
* `.autocomplete__trigger[data-disabled="true"]` - 触发器禁用状态
* `.autocomplete__value[data-placeholder="true"]` - 占位符状态
* `.autocomplete__clear-button[data-empty="true"]` - 无选择时隐藏清除按钮
* `.autocomplete__indicator[data-open="true"]` - 打开时的指示器状态
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Hover**:触发器上的 `:hover` 或 `[data-hovered="true"]`
* **Focus**:触发器上的 `:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:Autocomplete 上的 `:disabled` 或 `[data-disabled="true"]`
* **Open**:指示器上的 `[data-open="true"]`
## API 参考
### Autocomplete
| Prop | 类型 | 默认值 | 描述 |
| ----------------------- | --------------------------------------- | ------------------ | --------------------------------------------------------- |
| `placeholder` | `string` | `'Select an item'` | 为空时显示的占位文本 |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | 启用单选或多选 |
| `allowsEmptyCollection` | `boolean` | `false` | 是否允许空集合;为 true 时无选项也可使用 |
| `isOpen` | `boolean` | - | 弹出层打开状态(受控) |
| `defaultOpen` | `boolean` | - | 弹出层默认打开状态(非受控) |
| `onOpenChange` | `(isOpen: boolean) => void` | - | 打开状态变化时的回调 |
| `disabledKeys` | `Iterable` | - | 禁用项的 key |
| `isDisabled` | `boolean` | - | 是否禁用 |
| `value` | `Key \| Key[] \| null` | - | 当前值(受控) |
| `defaultValue` | `Key \| Key[] \| null` | - | 默认值(非受控) |
| `onChange` | `(value: Key \| Key[] \| null) => void` | - | 值变化时的回调 |
| `isRequired` | `boolean` | - | 是否必填 |
| `isInvalid` | `boolean` | - | 值是否无效 |
| `name` | `string` | - | 提交 HTML 表单时使用的名称 |
| `fullWidth` | `boolean` | `false` | 是否占满容器宽度 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调无阴影,适用于 Surface 内 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | 内容或 render 函数 |
### Autocomplete.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | --- | ---------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | 触发器内容或 render 函数 |
### Autocomplete.Value
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | --- | -------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | 值内容或 render 函数 |
### Autocomplete.Indicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | 自定义指示器内容 |
### Autocomplete.ClearButton
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------ | --- | ----------- |
| `className` | `string` | - | 附加 CSS 类 |
| `onClick` | `(e: MouseEvent) => void` | - | 点击按钮时的回调 |
| `ref` | `RefObject` | - | 清除按钮元素的 ref |
### Autocomplete.Popover
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ----------- |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | 弹出层相对触发器的位置 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | 子内容 |
### Autocomplete.Filter
| Prop | 类型 | 默认值 | 描述 |
| --------------- | ------------------------------------------ | --- | --------------------------- |
| `filter` | `(text: string, input: string) => boolean` | - | 自定义过滤函数 |
| `inputValue` | `string` | - | 受控输入值 |
| `onInputChange` | `(value: string) => void` | - | 输入值变化时的回调 |
| `children` | `ReactNode` | - | 过滤内容(SearchField 与 ListBox) |
### useFilter Hook
React Aria 的 `useFilter` hook 提供自动完成过滤函数。
```tsx
import {useFilter} from "@heroui/react";
const {contains} = useFilter({sensitivity: "base"});
...
...
```
**Options:**
| Option | 类型 | 默认值 | 描述 |
| ------------- | ------------------------------------------- | -------- | -------------- |
| `sensitivity` | `"base" \| "accent" \| "case" \| "variant"` | `"base"` | 匹配的 locale 敏感度 |
**Returns:**
| Function | 类型 | 描述 |
| ------------ | ------------------------------------------------ | ------------ |
| `contains` | `(string: string, substring: string) => boolean` | 判断字符串是否包含子串 |
| `startsWith` | `(string: string, substring: string) => boolean` | 判断字符串是否以子串开头 |
| `endsWith` | `(string: string, substring: string) => boolean` | 判断字符串是否以子串结尾 |
### Render Props
使用 Autocomplete.Value 的 render 函数时,提供以下值:
| Prop | 类型 | 描述 |
| ----------------- | --------------- | ----------------------------------------------- |
| `defaultChildren` | `ReactNode` | 默认渲染的值。多选模式下为每个选中项 children 的副本,以符合当前语言环境的分隔符连接 |
| `isPlaceholder` | `boolean` | 是否为占位符 |
| `state` | `SelectState` | 自动完成的状态。如需获取选中的集合节点,请使用 `state.selectedItems` |
| `selectedItems` | `(T \| null)[]` | 当前选中项的值 |
| `selectedText` | `string` | 选中项的 `textValue`,以符合当前语言环境的分隔符连接 |
## 无障碍
Autocomplete 组件实现带过滤的 ARIA select 模式,提供:
* 完整键盘导航支持
* 选择变化的屏幕阅读器播报
* 正确的焦点管理
* 禁用状态支持
* 带过滤的搜索功能
* HTML 表单集成
更多信息请参阅 [React Aria Select 文档](https://react-spectrum.adobe.com/react-aria/Select.html)。
## 相关组件
## Related Components
* **Listbox**: Scrollable list of selectable items
* **Popover**: Displays content in context with a trigger
* **Input**: Single-line text input built on React Aria
# ComboBox 组合框
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/combo-box
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(pickers)/combo-box.mdx
> 将文本输入与 ListBox 结合,用户可通过输入查询把选项列表过滤为匹配项
## 用法
```tsx
import { ComboBox } from '@heroui/react';
```
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function Default() {
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
);
}
```
## 组件结构
```tsx
import { ComboBox, Input, Label, Description, Header, ListBox, Separator } from '@heroui/react';
export default () => (
{/* 展示已选中的值,主要用于多选 */}
)
```
## 示例
### 宽度充满
```tsx
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function FullWidth() {
return (
最喜欢的动物
土豚
猫
狗
);
}
```
### 带描述
```tsx
"use client";
import {ComboBox, Description, Input, Label, ListBox} from "@heroui/react";
export function WithDescription() {
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
搜索并选择你最喜欢的动物
);
}
```
### 必填
```tsx
"use client";
import {Button, ComboBox, FieldError, Form, Input, Label, ListBox} from "@heroui/react";
export function Required() {
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert("表单提交成功!");
};
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
提交
);
}
```
### 禁用
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function Disabled() {
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
);
}
```
### 含禁用选项
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function WithDisabledOptions() {
return (
动物
狗
猫
鸟
袋鼠
象
老虎
);
}
```
### 分组选项
```tsx
"use client";
import {ComboBox, Header, Input, Label, ListBox, Separator} from "@heroui/react";
export function WithSections() {
return (
国家
美国
加拿大
墨西哥
英国
法国
德国
西班牙
意大利
日本
中国
印度
韩国
);
}
```
### 受控组件
```tsx
"use client";
import type {Key} from "@heroui/react";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const animals = [
{
id: "cat",
name: "猫",
},
{
id: "dog",
name: "狗",
},
{
id: "bird",
name: "鸟",
},
{
id: "fish",
name: "鱼",
},
{
id: "hamster",
name: "仓鼠",
},
];
const [selectedKey, setSelectedKey] = useState("cat");
const selectedAnimal = animals.find((a) => a.id === selectedKey);
return (
);
}
```
### 受控输入值
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
import {useState} from "react";
export function ControlledInputValue() {
const [inputValue, setInputValue] = useState("");
return (
);
}
```
### 异步加载
```tsx
"use client";
import {
Collection,
ComboBox,
EmptyState,
Input,
Label,
ListBox,
ListBoxLoadMoreItem,
Spinner,
} from "@heroui/react";
import {useAsyncList} from "@react-stately/data";
interface Character {
name: string;
}
export function AsynchronousLoading() {
const list = useAsyncList({
async load({cursor, filterText, signal}) {
if (cursor) {
cursor = cursor.replace(/^http:\/\//i, "https://");
}
const res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {
signal,
});
const json = await res.json();
return {
cursor: json.next,
items: json.results,
};
},
});
return (
选择角色
}>
{(item) => (
{item.name}
)}
加载更多…
);
}
```
### 默认选中项
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function DefaultSelectedKey() {
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
);
}
```
### 允许自定义值
```tsx
"use client";
import {ComboBox, Description, Input, Label, ListBox} from "@heroui/react";
export function AllowsCustomValue() {
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
可输入任意动物名称,即使不在列表中
);
}
```
### 自定义指示器
```tsx
"use client";
import {ChevronsExpandVertical} from "@gravity-ui/icons";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function CustomIndicator() {
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
);
}
```
### 自定义展示值
```tsx
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
ComboBox,
Description,
Input,
Label,
ListBox,
} from "@heroui/react";
export function CustomValue() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
return (
用户
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### 自定义过滤
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function CustomFiltering() {
const animals = [
{id: "cat", name: "猫"},
{id: "dog", name: "狗"},
{id: "bird", name: "鸟"},
{id: "fish", name: "鱼"},
{id: "hamster", name: "仓鼠"},
];
return (
{
if (!inputValue) return true;
return text.toLowerCase().includes(inputValue.toLowerCase());
}}
>
动物(自定义筛选)
{animals.map((animal) => (
{animal.name}
))}
);
}
```
### 渲染函数
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function RenderFunction() {
return (
}>
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
);
}
```
### 菜单触发方式
使用 `menuTrigger` prop 控制 Popover 何时打开:
* `focus`(默认):输入框获得焦点时打开 Popover
* `input`:用户编辑输入文本时打开 Popover
* `manual`:仅当用户按下触发按钮或使用方向键时打开 Popover
```tsx
"use client";
import {ComboBox, Description, Input, Label, ListBox} from "@heroui/react";
export function MenuTrigger() {
return (
);
}
```
### 表单值
使用 `formValue` 控制提交表单时提交选中项的 key 还是文本。默认值为 `key`。当 `allowsCustomValue` 为 true 时,始终提交文本。
```tsx
"use client";
import {
Button,
ComboBox,
Description,
FieldError,
Form,
Input,
Label,
ListBox,
} from "@heroui/react";
import {useState} from "react";
const animals = [
{id: "aardvark", name: "土豚"},
{id: "cat", name: "猫"},
{id: "dog", name: "狗"},
{id: "kangaroo", name: "袋鼠"},
{id: "panda", name: "熊猫"},
{id: "snake", name: "蛇"},
];
export function FormValue() {
const [message, setMessage] = useState(null);
return (
{
e.preventDefault();
const formData = new FormData(e.currentTarget);
setMessage(`key:${formData.get("animal")}。文本:${formData.get("animal-text")}。`);
}}
>
动物
{animals.map((animal) => (
{animal.name}
))}
提交选中项的 key
动物(文本)
{animals.map((animal) => (
{animal.name}
))}
提交选中项的文本
提交
{!!message && {message}
}
);
}
```
### 校验行为
使用 `validationBehavior` 控制校验信息的展示方式:
* `native`(默认):值为空或无效时阻止表单提交
* `aria`:实时显示错误,不阻止提交
```tsx
"use client";
import {
Button,
ComboBox,
Description,
FieldError,
Form,
Input,
Label,
ListBox,
} from "@heroui/react";
import {useState} from "react";
const animals = [
{id: "aardvark", name: "土豚"},
{id: "cat", name: "猫"},
{id: "dog", name: "狗"},
{id: "kangaroo", name: "袋鼠"},
{id: "panda", name: "熊猫"},
{id: "snake", name: "蛇"},
];
export function ValidationBehavior() {
const [nativeMessage, setNativeMessage] = useState(null);
const [ariaMessage, setAriaMessage] = useState(null);
return (
);
}
```
### 自定义校验
使用 `validate` 在值无效时返回错误信息,有效时返回 `true`。
```tsx
"use client";
import {Button, ComboBox, FieldError, Form, Input, Label, ListBox} from "@heroui/react";
const animals = [
{id: "aardvark", name: "土豚"},
{id: "cat", name: "猫"},
{id: "dog", name: "狗"},
{id: "kangaroo", name: "袋鼠"},
{id: "panda", name: "熊猫"},
{id: "snake", name: "蛇"},
];
export function CustomValidation() {
return (
{
e.preventDefault();
alert("表单提交成功!");
}}
>
{
if (value.selectedKey == null) {
return "请选择一种动物";
}
if (value.selectedKey === "snake") {
return "不允许选择蛇";
}
return true;
}}
>
最喜欢的动物
{animals.map((animal) => (
{animal.name}
))}
提交
);
}
```
### 只读
使用 `isReadOnly` 将 ComboBox 设为只读。选中值可以获得焦点,但不能修改。
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
const animals = [
{id: "aardvark", name: "土豚"},
{id: "cat", name: "猫"},
{id: "dog", name: "狗"},
{id: "kangaroo", name: "袋鼠"},
{id: "panda", name: "熊猫"},
{id: "snake", name: "蛇"},
];
export function ReadOnly() {
return (
最喜欢的动物
{animals.map((animal) => (
{animal.name}
))}
);
}
```
### 多选
设置 `selectionMode="multiple"` 以允许选择多个选项。在多选模式下,使用 `ComboBox.Value` 展示已选项,并同时给内部的 `ListBox` 传入 `selectionMode="multiple"`。选择通过 `value` / `defaultValue`(`Key[]`)以及 `onChange` 回调来控制。
```tsx
"use client";
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
export function MultipleSelection() {
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"` 以应用适合 Surface 背景的低强调变体。
```tsx
"use client";
import {Button, ComboBox, FieldError, Form, Input, Label, ListBox, Surface} from "@heroui/react";
export function OnSurface() {
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data: Record = {};
formData.forEach((value, key) => {
data[key] = value.toString();
});
alert("表单提交成功!");
};
return (
最喜欢的动物
土豚
猫
狗
袋鼠
熊猫
蛇
提交
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ComboBox, Input, Label, ListBox} from "@heroui/react";
const itemClass =
"rounded-lg data-[focused=true]:bg-muted/70 data-[selected=true]:font-medium data-[selected=true]:text-foreground";
export function CustomStyles() {
return (
框架
React
Vue
Svelte
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 ComboBox 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.combo-box {
@apply flex flex-col gap-1;
}
.combo-box__input-group {
@apply relative inline-flex items-center;
}
.combo-box__trigger {
@apply absolute right-0 text-muted;
}
.combo-box__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ComboBox 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/combo-box.css)):
#### 基础类 \[!toc]
* `.combo-box` - ComboBox 根容器
* `.combo-box__input-group` - 输入框与触发按钮的容器
* `.combo-box__value` - 已选值的展示区域(用于多选)
* `.combo-box__trigger` - 打开 Popover 的按钮
* `.combo-box__popover` - Popover 容器
#### 状态类 \[!toc]
* `.combo-box[data-invalid="true"]` - 无效状态
* `.combo-box[data-disabled="true"]` - 禁用状态
* `.combo-box__trigger[data-focus-visible="true"]` - 触发器聚焦
* `.combo-box__trigger[data-disabled="true"]` - 触发器禁用
* `.combo-box__trigger[data-open="true"]` - 展开状态
### 交互状态
组件同时支持 CSS 伪类与 data 属性:
* **Hover**:触发器上 `:hover` 或 `[data-hovered="true"]`
* **Focus**:触发器上 `:focus-visible` 或 `[data-focus-visible="true"]`
* **Disabled**:ComboBox 上 `:disabled` 或 `[data-disabled="true"]`
* **Open**:触发器上 `[data-open="true"]`
## API 参考
### ComboBox
| Prop | 类型 | 默认值 | 描述 |
| ----------------------- | ---------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------ |
| `inputValue` | `string` | - | 当前输入值(受控) |
| `defaultInputValue` | `string` | - | 默认输入值(非受控) |
| `onInputChange` | `(value: string) => void` | - | 输入值变化时的回调 |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | 启用单选还是多选 |
| `selectedKey` | `Key \| null` | - | 当前选中的 key(受控,单选) |
| `defaultSelectedKey` | `Key \| null` | - | 默认选中的 key(非受控,单选) |
| `onSelectionChange` | `(key: Key \| null) => void` | - | 选中变化时的回调(单选) |
| `value` | `Key \| null \| Key[]` | - | 当前选中的 key(受控)。当 `selectionMode="multiple"` 时为 `Key[]` |
| `defaultValue` | `Key \| null \| Key[]` | - | 初始选中的 key(非受控)。当 `selectionMode="multiple"` 时为 `Key[]` |
| `onChange` | `(value: Key \| null \| Key[]) => void` | - | 选中变化时的回调 |
| `items` | `Iterable` | - | 在 ListBox 中展示的 items |
| `disabledKeys` | `Iterable` | - | 禁用项的 key |
| `defaultFilter` | `(text: string, inputValue: string) => boolean` | - | 用于过滤 items 的自定义过滤函数 |
| `isDisabled` | `boolean` | - | 是否禁用 ComboBox |
| `isReadOnly` | `boolean` | - | 输入是否可选中但不可由用户更改 |
| `isRequired` | `boolean` | - | 是否必填 |
| `isInvalid` | `boolean` | - | ComboBox 的值是否无效 |
| `validate` | `(value: ComboBoxValidationValue) => ValidationError \| true \| null \| undefined` | - | 若给定值无效则返回错误信息的函数。当 `validationBehavior="native"` 时,提交表单会向用户展示校验错误;实时校验请改用 `isInvalid` prop |
| `validationBehavior` | `"native" \| "aria"` | `"native"` | 使用原生 HTML 表单校验在值缺失或无效时阻止提交,还是通过 ARIA 将字段标记为必填或无效 |
| `name` | `string` | - | 提交 HTML 表单时 input 的 name |
| `form` | `string` | - | 要关联的 `` 元素 id |
| `formValue` | `"text" \| "key"` | `"key"` | 在 HTML 表单提交时提交选中项的文本还是 key。当 `allowsCustomValue` 为 `true` 时该选项不适用,始终提交文本 |
| `autoComplete` | `string` | - | 自动完成行为类型 |
| `autoFocus` | `boolean` | - | 是否在挂载时自动聚焦 |
| `allowsCustomValue` | `boolean` | - | 是否允许不在列表中的自定义值 |
| `allowsEmptyCollection` | `boolean` | - | 是否允许空集合 |
| `menuTrigger` | `"focus" \| "input" \| "manual"` | `"focus"` | 展示 ComboBox 菜单所需的交互 |
| `shouldFocusWrap` | `boolean` | - | 键盘导航是否循环 |
| `fullWidth` | `boolean` | `false` | ComboBox 是否占满容器宽度 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式;`secondary` 为低强调无阴影,适用于 Surface 内 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| RenderFunction` | - | ComboBox 内容或 render 函数 |
| `render` | `DOMRenderFunction` | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
### ComboBox.InputGroup
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ------------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | InputGroup 内容 |
### ComboBox.Value
渲染 ComboBox 已选中的值,若未选中任何值则渲染占位符。默认情况下,已选项以逗号分隔的列表形式渲染。可使用 render 函数自定义(例如以标签形式展示)。
| Prop | 类型 | 默认值 | 描述 |
| ------------- | -------------------------------------------------------------- | --- | ----------------- |
| `placeholder` | `ReactNode` | - | 未选中任何项时展示的值 |
| `className` | `string \| (values: ComboBoxValueRenderProps) => string` | - | 附加 CSS 类 |
| `children` | `ReactNode \| (values: ComboBoxValueRenderProps) => ReactNode` | - | 自定义已选值的 render 函数 |
### ComboBox.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | -------- |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | 自定义触发器内容 |
### ComboBox.Popover
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------ |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | 相对于触发器的 Popover 位置 |
| `className` | `string` | - | 附加 CSS 类 |
| `children` | `ReactNode` | - | 子内容 |
### Render Props
对 ComboBox 使用 render 函数时,会传入以下值:
| Prop | 类型 | 描述 |
| -------------- | --------------- | ----------- |
| `state` | `ComboBoxState` | ComboBox 状态 |
| `inputValue` | `string` | 当前输入值 |
| `selectedKey` | `Key \| null` | 当前选中的 key |
| `selectedItem` | `Node \| null` | 当前选中的 item |
## 无障碍
ComboBox 实现 ARIA ComboBox 模式,并提供:
* 完整键盘导航
* 选择与输入变化时的屏幕阅读器播报
* 合理的焦点管理
* 禁用状态支持
* 输入过滤(typeahead)式搜索
* 与 HTML 表单的集成
* 自定义值支持
更多信息见 [React Aria ComboBox 文档](https://react-spectrum.adobe.com/react-aria/ComboBox.html)。
## 相关组件
## Related Components
* **Listbox**: Scrollable list of selectable items
* **Popover**: Displays content in context with a trigger
* **Input**: Single-line text input built on React Aria
# Select 选择器
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/select
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(pickers)/select.mdx
> Select 展示可折叠的选项列表,并允许用户从中选择一项。
## 用法
```tsx
import { Select } from "@heroui/react";
```
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function Default() {
return (
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
);
}
```
## 组件结构
```tsx
import {Select, Label, Description, Header, ListBox, Separator} from "@heroui/react";
export default () => (
);
```
## 示例
### 变体
Select 组件支持两种视觉变体:
* **`primary`**(默认)— 带阴影的标准样式,适用于大多数场景
* **`secondary`** — 低强调、无阴影,适合在 Surface 等表面背景上使用
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function Variants() {
return (
主要变体
Option 1
Option 2
次要变体
Option 1
Option 2
);
}
```
### 宽度充满
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function FullWidth() {
return (
喜爱的动物
猫
狗
鸟
);
}
```
### 带描述
```tsx
import {Description, Label, ListBox, Select} from "@heroui/react";
export function WithDescription() {
return (
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
请选择居住州
);
}
```
### 必填
```tsx
"use client";
import {Button, FieldError, Form, Label, ListBox, Select} from "@heroui/react";
export function Required() {
const onSubmit = (e: React.SyntheticEvent) => {
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("表单提交成功!");
};
return (
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
国家
美国
加拿大
墨西哥
英国
法国
德国
提交
);
}
```
### 禁用
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function Disabled() {
return (
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
拟访问国家
阿根廷
委内瑞拉
日本
法国
意大利
西班牙
);
}
```
### 含禁用选项
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function WithDisabledOptions() {
return (
动物
狗
猫
鸟
袋鼠
大象
老虎
);
}
```
### 多选
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function MultipleSelect() {
return (
拟访问国家
阿根廷
委内瑞拉
日本
法国
意大利
西班牙
泰国
新西兰
冰岛
);
}
```
### 分组选项
```tsx
import {Header, Label, ListBox, Select, Separator} from "@heroui/react";
export function WithSections() {
return (
国家
美国
加拿大
墨西哥
英国
法国
德国
西班牙
意大利
日本
中国
印度
韩国
);
}
```
### 受控组件
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
export function Controlled() {
const states = [
{
id: "california",
name: "加利福尼亚",
},
{
id: "texas",
name: "德克萨斯",
},
{
id: "florida",
name: "佛罗里达",
},
{
id: "new-york",
name: "纽约",
},
{
id: "illinois",
name: "伊利诺伊",
},
{
id: "pennsylvania",
name: "宾夕法尼亚",
},
];
const [state, setState] = useState("california");
const selectedState = states.find((s) => s.id === state);
return (
setState(value)}
>
州(受控)
{states.map((state) => (
{state.name}
))}
已选:{selectedState?.name || "无"}
);
}
```
### 受控多选
```tsx
"use client";
import type {Key} from "@heroui/react";
import {Label, ListBox, Select} from "@heroui/react";
import React from "react";
export function ControlledMultiple() {
const [selected, setSelected] = React.useState(["california", "texas"]);
return (
setSelected(keys as Key[])}
>
州(受控多选)
加利福尼亚
德克萨斯
佛罗里达
纽约
伊利诺伊
宾夕法尼亚
已选:{selected.length > 0 ? selected.join(", ") : "无"}
);
}
```
### 受控展开状态
```tsx
"use client";
import {Button, Label, ListBox, Select} from "@heroui/react";
import {useState} from "react";
export function ControlledOpenState() {
const [isOpen, setIsOpen] = useState(false);
return (
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
setIsOpen(!isOpen)}>{isOpen ? "关闭" : "打开"}选择框
选择框{isOpen ? "已打开" : "已关闭"}
);
}
```
### 异步加载
```tsx
"use client";
import {Label, ListBox, Select, Spinner} from "@heroui/react";
import {useAsyncList} from "@react-stately/data";
import {Collection, ListBoxLoadMoreItem} from "react-aria-components";
interface Pokemon {
name: string;
}
export function AsynchronousLoading() {
const list = useAsyncList({
async load({cursor, signal}) {
const res = await fetch(cursor || `https://pokeapi.co/api/v2/pokemon`, {signal});
const json = await res.json();
return {
cursor: json.next,
items: json.results,
};
},
});
return (
选择宝可梦
{(item: Pokemon) => (
{item.name}
)}
加载更多…
);
}
```
### 自定义指示器
```tsx
import {ChevronsExpandVertical} from "@gravity-ui/icons";
import {Label, ListBox, Select} from "@heroui/react";
export function CustomIndicator() {
return (
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
);
}
```
### 带清除按钮
`Select.Trigger` 本身是 button,因此不能再嵌套 `` 来实现清除。请在触发器内组合 `Select.ClearButton` — 它渲染为非 button 控件,且不会打开菜单。
```tsx
import {Label, ListBox, Select} from "@heroui/react";
export function WithClearButton() {
return (
State
Florida
Delaware
California
Texas
New York
Washington
);
}
```
由于 ARIA 将 button 的子节点视为纯展示内容,清除控件无法获得焦点,因此它带有 `aria-hidden`,仅作为指针操作的可视入口。当组合了 `Select.ClearButton` 时,触发器同时支持按 `Backspace` 或 `Delete` 清除,这是键盘与屏幕阅读器用户的清除方式。两种方式都会调用 `onClear`。
### 自定义展示值
```tsx
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
Description,
Label,
ListBox,
Select,
} from "@heroui/react";
export function CustomValue() {
const users = [
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John",
},
{
avatarUrl: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane",
},
];
return (
用户
{({defaultChildren, isPlaceholder, state}) => {
if (isPlaceholder || state.selectedItems.length === 0) {
return defaultChildren;
}
const selectedItems = state.selectedItems;
if (selectedItems.length > 1) {
return `${selectedItems.length} 位用户已选`;
}
const selectedItem = users.find((user) => user.id === selectedItems[0]?.key);
if (!selectedItem) {
return defaultChildren;
}
return (
{selectedItem.fallback}
{selectedItem.name}
);
}}
{users.map((user) => (
{user.fallback}
{user.name}
{user.email}
))}
);
}
```
### 渲染函数
```tsx
"use client";
import {Label, ListBox, Select} from "@heroui/react";
export function RenderFunction() {
return (
}
>
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
);
}
```
### 表面样式
在 [Surface](/docs/react/components/surface) 内使用时,请使用 `variant="secondary"`,以应用适合表面背景的低强调变体。
```tsx
"use client";
import {Button, FieldError, Form, Label, ListBox, Select, Surface} from "@heroui/react";
export function OnSurface() {
const onSubmit = (e: React.SyntheticEvent) => {
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("表单提交成功!");
};
return (
州
佛罗里达
特拉华
加利福尼亚
德克萨斯
纽约
华盛顿
国家
美国
加拿大
墨西哥
英国
法国
德国
提交
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Label, ListBox, Select} from "@heroui/react";
const itemClass =
"rounded-lg data-[focused=true]:bg-accent/10 data-[selected=true]:text-foreground";
export function CustomStyles() {
return (
套餐
免费
专业版
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.select {
@apply flex flex-col gap-1;
}
.select__trigger {
@apply rounded-lg border border-border bg-surface p-2;
}
.select__value {
@apply text-current;
}
.select__indicator {
@apply text-muted;
}
.select__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Select 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/select.css)):
#### 基础类 \[!toc]
* `.select` - Select 根容器
* `.select__trigger` - 打开下拉的触发按钮
* `.select__value` - 当前显示的值或占位符
* `.select__clear-button` - 触发器内的可选清除控件
* `.select__indicator` - 下拉指示图标
* `.select__popover` - 弹出层容器
#### 变体类 \[!toc]
* `.select--primary` - Primary 变体,带阴影(默认)
* `.select--secondary` - Secondary 变体,无阴影,适合在 Surface 上使用
#### 状态类 \[!toc]
* `.select[data-invalid="true"]` - 无效状态
* `.select__trigger[data-focus-visible="true"]` - 触发器聚焦状态
* `.select__trigger[data-disabled="true"]` - 触发器禁用状态
* `.select__value[data-placeholder="true"]` - 占位符状态
* `.select__clear-button[data-empty="true"]` - 无选中项时隐藏清除控件
* `.select__indicator[data-open="true"]` - 展开时的指示器状态
### 交互状态
该组件同时支持 CSS 伪类与 data 属性,以提供更灵活的状态控制:
* **悬停**:触发器上的 `:hover` 或 `[data-hovered="true"]`
* **聚焦**:触发器上的 `:focus-visible` 或 `[data-focus-visible="true"]`
* **禁用**:Select 上的 `:disabled` 或 `[data-disabled="true"]`
* **展开**:指示器上的 `[data-open="true"]`
## API 参考
### Select
| Prop | 类型 | 默认值 | 描述 |
| --------------- | ------------------------------------------------------------------------- | ------------------ | --------------------------------------------------------------- |
| `placeholder` | `string` | `'Select an item'` | Select 为空时显示的占位符文本。 |
| `selectionMode` | `"single" \| "multiple"` | `"single"` | 启用单选或多选。 |
| `isOpen` | `boolean` | - | 设置菜单是否打开(受控)。 |
| `defaultOpen` | `boolean` | - | 设置菜单默认是否打开(非受控)。 |
| `onOpenChange` | `(isOpen: boolean) => void` | - | 展开状态变化时的事件处理函数。 |
| `disabledKeys` | `Iterable` | - | 禁用条目的 key。 |
| `isDisabled` | `boolean` | - | Select 是否禁用。 |
| `value` | `Key \| Key[] \| null` | - | 当前值(受控)。 |
| `defaultValue` | `Key \| Key[] \| null` | - | 默认值(非受控)。 |
| `onChange` | `(value: Key \| Key[] \| null) => void` | - | 值变化时的事件处理函数。 |
| `onClear` | `() => void` | - | 清除选择时的事件处理函数。 |
| `isRequired` | `boolean` | - | 用户输入是否必填。 |
| `isInvalid` | `boolean` | - | Select 的值是否无效。 |
| `name` | `string` | - | 输入框名称,用于提交 HTML 表单。 |
| `autoComplete` | `string` | - | 描述自动完成行为类型。 |
| `fullWidth` | `boolean` | `false` | Select 是否占满容器宽度。 |
| `variant` | `"primary" \| "secondary"` | `"primary"` | 视觉变体。`primary` 为默认带阴影样式。`secondary` 为低强调、无阴影变体,适合在 Surface 上使用。 |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | Select 内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Select.Trigger
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------------------- | --- | ----------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 触发器内容或渲染函数。 |
### Select.Value
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------ | --- | --------------------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode \| RenderFunction` | - | 值区域内容或渲染函数。 |
| `render` | `DOMRenderFunction` | - | 使用自定义渲染函数覆盖默认 DOM 元素。 |
### Select.Indicator
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------- | --- | ---------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode` | - | 自定义指示器内容。 |
### Select.ClearButton
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------- | --- | --------------- |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode` | - | 自定义内容,替代默认图标。 |
| `onClick` | `(e: MouseEvent) => void` | - | 点击清除控件时的事件处理函数。 |
`Select.ClearButton` 渲染为 `span`,因此可以放在 `Select.Trigger`(button)内部而不会形成嵌套 button。选择为空时会自动隐藏。
### Select.Popover
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------ |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `"bottom"` | 弹出层相对触发器的位置。 |
| `className` | `string` | - | 额外的 CSS 类。 |
| `children` | `ReactNode` | - | 子内容。 |
### RenderProps
对 `Select.Value` 使用渲染函数时,会提供以下值:
| Prop | 类型 | 描述 |
| ----------------- | ------------- | ----------- |
| `defaultChildren` | `ReactNode` | 默认渲染的值。 |
| `isPlaceholder` | `boolean` | 是否为占位符状态。 |
| `state` | `SelectState` | Select 的状态。 |
| `selectedItems` | `Node[]` | 当前已选中的条目。 |
## 无障碍
Select 组件实现 ARIA 列表框模式,并提供:
* 完整的键盘导航支持
* 选择变化时的屏幕阅读器播报
* 合理的焦点管理
* 禁用状态支持
* 输入首字母快速定位(typeahead)
* 与 HTML 表单的集成
更多信息见 [React Aria Select 文档](https://react-spectrum.adobe.com/react-aria/Select.html)。
## 相关组件
## Related Components
* **Listbox**: Scrollable list of selectable items
* **Popover**: Displays content in context with a trigger
* **Label**: Accessible label for form controls
# Kbd 键盘按键
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/kbd
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(typography)/kbd.mdx
> 展示键盘快捷键与按键组合
## 用法
```tsx
import { Kbd } from "@heroui/react";
```
```tsx
import {Kbd} from "@heroui/react";
export function Basic() {
return (
K
P
C
D
);
}
```
## 组件结构
```tsx
import { Kbd } from "@heroui/react";
export default () => (
⌘
K
);
```
## 示例
### 变体
```tsx
import {Kbd} from "@heroui/react";
export function Variants() {
return (
复制:
C
C
粘贴:
V
V
剪切:
X
X
撤销:
Z
Z
重做:
Z
Z
);
}
```
### 导航按键
```tsx
import {Kbd} from "@heroui/react";
export function NavigationKeys() {
return (
);
}
```
### 行内用法
```tsx
import {Kbd} from "@heroui/react";
export function InlineUsage() {
return (
按{" "}
Esc
{" "}
关闭对话框。
使用{" "}
K
{" "}
打开命令面板。
使用{" "}
{" "}
和{" "}
{" "}
方向键进行导航。
使用{" "}
S
{" "}
定期保存你的工作。
);
}
```
### 说明文字
```tsx
import {Kbd} from "@heroui/react";
export function InstructionalText() {
return (
快捷操作
• 打开搜索:{" "}
K
• 切换侧边栏:{" "}
B
• 新建文件:{" "}
N
• 快速保存:{" "}
S
);
}
```
### 特殊按键
```tsx
import {Kbd} from "@heroui/react";
export function SpecialKeys() {
return (
按{" "}
{" "}
确认,或按{" "}
{" "}
取消。
使用{" "}
{" "}
在表单字段间切换,使用{" "}
{" "}
返回上一项。
按住{" "}
{" "}
可临时启用平移模式。
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {Kbd} from "@heroui/react";
export function CustomStyles() {
return (
K
P
);
}
```
### 全局 CSS
可使用 `@layer components` 指令自定义 Kbd 组件类。
[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.kbd {
@apply bg-gray-100 dark:bg-gray-800 border-gray-300;
}
.kbd__abbr {
@apply font-bold;
}
.kbd__content {
@apply text-sm;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Kbd 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/kbd.css)):
#### 基础类 \[!toc]
* `.kbd` - 基础按键样式,含背景、边框与间距
* `.kbd__abbr` - 修饰键的缩写元素
* `.kbd__content` - 按键文本的内容包裹层
## API 参考
### Kbd
| Prop | 类型 | 默认值 | 描述 | |
| ----------- | ----------------- | --------- | --------- | ------ |
| `children` | `React.ReactNode` | - | 按键内容 | |
| `variant` | \`"default" | "light"\` | `default` | 按键视觉变体 |
| `className` | `string` | - | 自定义 CSS 类 | |
### Kbd.Abbr
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | ------------------------------ |
| `title` | `string` | - | 无障碍 title 属性(如 ⌘ 对应 "Command") |
| `children` | `React.ReactNode` | - | 要显示的符号或文本(如 ⌘、⌥、⇧) |
| `className` | `string` | - | 自定义 CSS 类 |
### Kbd.Key
| Prop | 类型 | 默认值 | 描述 |
| ----------- | ----------------- | --- | --------- |
| `children` | `React.ReactNode` | - | 按键文本内容 |
| `className` | `string` | - | 自定义 CSS 类 |
### Kbd.Content Type
`keyValue` prop 可用的按键值:
| Modifier Keys | Special Keys | Navigation Keys | Function Keys |
| ------------- | ------------ | --------------- | ------------- |
| `command` | `enter` | `up` | `fn` |
| `shift` | `delete` | `down` | |
| `ctrl` | `escape` | `left` | |
| `option` | `tab` | `right` | |
| `alt` | `space` | `pageup` | |
| `win` | `capslock` | `pagedown` | |
| | `help` | `home` | |
| | | `end` | |
## 相关组件
# Typography 排版
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/typography
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(typography)/typography.mdx
> 面向标题、正文与行内代码的语义化排版原语,基于 React Aria Components 的 Text 构建。
## 用法
```tsx
import {Typography} from "@heroui/react";
```
```tsx
import {Typography} from "@heroui/react";
export const Default = () => {
return (
打造更出色的界面
保持语义化的排版
默认可组合
小标题
HeroUI Typography 以 React Aria Components Text 为原语,提供语义化排版类型与 render prop
多态。
用于次要说明的较小弱化正文。
pnpm add @heroui/react
);
};
```
## 组件结构
默认情况下,`Typography` 会将视觉上的 `type` 映射到对应的语义化元素。
```tsx
import {Typography} from "@heroui/react";
export default () => (
Heading
);
```
## 示例
### 层级
```tsx
import {Typography} from "@heroui/react";
const scale = [
{
label: "h1",
meta: "36px / 600 / 1.11 / tight",
sample: "打造更出色的界面",
type: "h1" as const,
},
{
label: "h2",
meta: "30px / 600 / 1.17 / tight",
sample: "为智能时代而生",
type: "h2" as const,
},
{
label: "h3",
meta: "24px / 600 / 1.25 / tight",
sample: "按您的条件定价",
type: "h3" as const,
},
{
label: "h4",
meta: "20px / 600 / 1.33 / tight",
sample: "申请创业计划",
type: "h4" as const,
},
{
label: "h5",
meta: "18px / 600 / 1.39 / tight",
sample: "卡片标题",
type: "h5" as const,
},
{
label: "h6",
meta: "16px / 600 / 1.50 / tight",
sample: "较小的功能标题",
type: "h6" as const,
},
{
label: "body",
meta: "16px / 400 / 1.75",
sample: "用于文档、营销文案与描述的主要正文。",
type: "body" as const,
},
{
label: "body-sm",
meta: "14px / 400 / 1.50",
sample: "次要正文、表格单元格、导航与侧边栏项。",
type: "body-sm" as const,
},
{
label: "body-xs",
meta: "12px / 400 / 1.25",
sample: "说明文字、徽章、辅助文本与细则。",
type: "body-xs" as const,
},
{
label: "code",
meta: "14px / mono",
sample: "pnpm add @heroui/react",
type: "code" as const,
},
] as const;
export const TypographyScale = () => {
return (
{scale.map((row) => (
{row.label}
{row.meta}
{row.sample}
))}
);
};
```
### 原语
```tsx
import {Typography} from "@heroui/react";
export const Primitives = () => {
return (
仪表盘
便捷原语是 Typography 的薄封装,可在不学习第二套样式系统的情况下选择显式组合。
Paragraph 支持 base、sm 和 xs 尺寸。
Typography.Code
);
};
```
* `Typography.Heading` 将 `level={1..6}` 映射为 `type="h1"` 至 `type="h6"`。
* `Typography.Paragraph` 将 `size="base" | "sm" | "xs"` 映射为正文样式。
* `Typography.Code` 映射为行内代码样式。
* `Typography.Prose` 为以常规 HTML 子节点传入的富文本内容提供排版样式。
### 富文本排版
```tsx
import {Typography} from "@heroui/react";
export const Prose = () => {
return (
正文标题
Prose 适用于标记已是语义化、由 HeroUI 应用默认排版节奏的写作型内容。
章节标题
行内代码如 render 与 Typography 原语获得相同的代码样式处理。
);
};
```
### 渲染属性
```tsx
"use client";
import {Typography} from "@heroui/react";
export const RenderProps = () => {
return (
{children} } type="h1">
H1 视觉样式,h2 语义元素
{children} }>
render prop 可更换底层元素,同时保留 HeroUI 的 props 与样式。
);
};
```
需要自定义实际渲染的元素时,可使用 React Aria Components 风格的 `render` prop。
## 自定义样式
### Tailwind CSS
```tsx
import {Typography} from "@heroui/react";
export function CustomStyles() {
return (
更新日志
更快的搜索结果
得益于改进的索引,查询现在在 200 毫秒内返回。
);
}
```
### 全局 CSS
若要自定义 Typography 组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.typography--h1 {
@apply font-extrabold tracking-tight;
}
.typography--code {
@apply rounded-md bg-default-100 font-mono dark:bg-default-50/20;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
Typography 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/typography.css)):
#### 基础类 \[!toc]
* `.typography` - 排版基础原语
* `.typography-prose` - 富文本文章体容器
#### 类型类 \[!toc]
* `.typography--h1` 至 `.typography--h6`
* `.typography--body`、`.typography--body-sm`、`.typography--body-xs`
* `.typography--code`
#### 修饰类 \[!toc]
* `.typography--align-start`、`.typography--align-center`、`.typography--align-end`、`.typography--align-justify`
* `.typography--color-default`、`.typography--color-muted`
* `.typography--truncate`
* `.typography--weight-normal`、`.typography--weight-medium`、`.typography--weight-semibold`、`.typography--weight-bold`
## API 参考
### Typography
| 属性 | 类型 | 默认值 | 说明 |
| ---------- | -------------------------------------------------------------------------------------------- | ----------- | ----------------------- |
| `type` | `'h1' \| 'h2' \| 'h3' \| 'h4' \| 'h5' \| 'h6' \| 'body' \| 'body-sm' \| 'body-xs' \| 'code'` | `'body'` | 语义化排版样式。 |
| `align` | `'start' \| 'center' \| 'end' \| 'justify'` | `'start'` | 文本对齐。 |
| `color` | `'default' \| 'muted'` | `'default'` | 文本颜色。 |
| `weight` | `'normal' \| 'medium' \| 'semibold' \| 'bold'` | - | 字重覆盖。 |
| `truncate` | `boolean` | - | 将文本截断为单行并显示省略号。 |
| `render` | `DOMRenderFunction` | - | 来自 React Aria 的自定义渲染函数。 |
| `children` | `ReactNode` | - | 文本内容。 |
## 相关组件
# ScrollShadow 滚动阴影
**Category**: react
**URL**: https://heroui.com/cn/docs/react/components/scroll-shadow
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/cn/react/components/(utilities)/scroll-shadow.mdx
> 通过阴影提示可滚动溢出内容,并根据滚动位置自动检测显示或隐藏。
## 用法
```tsx
import { ScrollShadow } from "@heroui/react";
```
```tsx
import {ScrollShadow} from "@heroui/react";
export default function Default() {
return (
{Array.from({length: 10}).map((_, idx) => (
段落 {idx + 1}:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit risus,
sed porttitor quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
);
}
```
## 示例
### 方向
```tsx
import {Card, ScrollShadow} from "@heroui/react";
const images = [
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/robot1.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/avocado.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/oranges.jpeg",
];
export default function Orientation() {
const getRandomImage = (idx: number) => {
return images[idx % images.length];
};
return (
垂直
{Array.from({length: 10}).map((_, idx) => (
段落 {idx + 1}:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit
risus, sed porttitor quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
水平
{Array.from({length: 10}).map((_, idx) => (
连接未来
今天 18:30
))}
);
}
```
### 阴影尺寸
```tsx
import {ScrollShadow} from "@heroui/react";
export default function Size() {
return (
{Array.from({length: 10}).map((_, idx) => (
段落 {idx + 1}:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit risus,
sed porttitor quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
);
}
```
### 与 Card 组合
```tsx
import {Button, Card, ScrollShadow} from "@heroui/react";
export default function WithCard() {
return (
条款与条件
继续前请先阅读
{Array.from({length: 10}).map((_, idx) => (
段落 {idx + 1}:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit risus,
sed porttitor quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
Cancel
接受
);
}
```
### 隐藏滚动条
```tsx
import {ScrollShadow} from "@heroui/react";
export default function HideScrollBar() {
return (
{Array.from({length: 10}).map((_, idx) => (
段落 {idx + 1}:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit risus,
sed porttitor quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
);
}
```
### 可见性变化
```tsx
"use client";
import type {ScrollShadowVisibility} from "@heroui/react";
import {Card, ScrollShadow} from "@heroui/react";
import {useState} from "react";
const images = [
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/robot1.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/avocado.jpeg",
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/oranges.jpeg",
];
const VISIBILITY_LABELS: Record = {
auto: "自动",
both: "两侧",
bottom: "底部",
left: "左侧",
none: "无",
right: "右侧",
top: "顶部",
};
export default function VisibilityChange() {
const [verticalState, setVerticalState] = useState("none");
const [horizontalState, setHorizontalState] = useState("none");
const getRandomImage = (idx: number) => {
return images[idx % images.length];
};
return (
垂直阴影状态:{VISIBILITY_LABELS[verticalState]}
setVerticalState(visibility)}
>
{Array.from({length: 10}).map((_, idx) => (
段落 {idx + 1}:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit
risus, sed porttitor quam. Morbi accumsan cursus enim, sed ultricies sapien.
))}
水平阴影状态:{VISIBILITY_LABELS[horizontalState]}
setHorizontalState(visibility)}
>
{Array.from({length: 10}).map((_, idx) => (
连接未来
今天 18:30
))}
);
}
```
## 自定义样式
### Tailwind CSS
```tsx
import {ScrollShadow} from "@heroui/react";
const entries = [
"与设计团队一起回顾季度目标。",
"将深色模式设计令牌发布到生产环境。",
"合并表单字段的无障碍修复。",
"发布更新后的组件文档。",
"安排下一冲刺的性能审计。",
"在文档站点中添加滚动阴影示例。",
];
export function CustomStyles() {
return (
{entries.map((entry) => (
{entry}
))}
);
}
```
### 全局 CSS
若要自定义组件类,可使用 `@layer components` 指令。[了解更多](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)。
```css
@layer components {
.scroll-shadow {
@apply rounded-xl border border-default-200;
}
.scroll-shadow--vertical {
@apply pr-2; /* Add padding for custom scrollbar styling */
}
.scroll-shadow--horizontal {
@apply pb-2;
}
}
```
## 样式参考
HeroUI 遵循 [BEM](https://getbem.com/) 方法论,确保组件变体与状态可复用且易于自定义。
### CSS 类
ScrollShadow 组件使用以下 CSS 类([查看源码样式](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/scroll-shadow.css)):
#### 基础类 \[!toc]
* `.scroll-shadow` - 根容器元素
#### 方向变体 \[!toc]
* `.scroll-shadow--vertical` - 纵向滚动(默认)
* `.scroll-shadow--horizontal` - 横向滚动
#### 状态修饰符 \[!toc]
* `.scroll-shadow--hide-scrollbar` - 隐藏原生滚动条
### CSS 变量
ScrollShadow 组件使用 CSS 变量设置渐变遮罩尺寸,并为可见的原生滚动条保留空间:
| 变量 | 默认值 | 描述 |
| -------------------------------- | -------------------------------- | ---------------------------------------------- |
| `--scroll-shadow-size` | `40px` | 控制渐变阴影尺寸。该值由 `size` prop 设置。 |
| `--scroll-shadow-offset` | `0px` | 开始显示渐变之前需要滚动的距离。该值由 `offset` prop 设置。 |
| `--scroll-shadow-scrollbar-size` | `10px`(`hideScrollBar` 时为 `0px`) | 为原生滚动条保留一段实色遮罩区域,避免渐变覆盖滚动条。使用更宽的自定义滚动条时可以覆盖该值。 |
### Data 属性
组件使用 data 属性控制阴影可见性:
* **滚动状态**:`[data-top-scroll]`、`[data-bottom-scroll]`、`[data-left-scroll]`、`[data-right-scroll]` — 当内容可向对应方向滚动时应用
* **组合状态**:`[data-top-bottom-scroll]`、`[data-left-right-scroll]` — 当内容可向两个方向滚动时应用
* **方向**:`[data-orientation="vertical"]` 或 `[data-orientation="horizontal"]` — 表示滚动方向
* **尺寸**:`[data-scroll-shadow-size]` — 阴影渐变尺寸数值
* **阴影模式**:`[data-scroll-shadow-mode]` — 渐变由滚动位置推导时为 `"auto"`;`visibility` 受控或 `isEnabled` 为 `false` 时为 `"manual"`
### 滚动驱动的渐变
在 `auto` 模式下,支持[滚动驱动动画](https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation-timeline)的浏览器会直接在 CSS
中根据滚动位置推导渐变。因此遮罩在首次绘制时就是正确的,无需测量,也不会在 hydration 期间出现未渐变内容的闪烁。
不支持的浏览器会回退到上面的 `[data-*-scroll]` 属性,这些属性在 hydration 之后才写入。自定义样式时需要注意两点:
* 在 `auto` 模式下,即使没有可滚动内容,根元素也始终会解析出 `mask-image`。这会使其成为层叠上下文,
并成为 `position: fixed` 后代元素的包含块。如需退出该行为,请显式设置 `visibility`。
* 滚动驱动的渐变依赖根元素上的 `animation` 属性。在同一元素上使用 `animate-*` 工具类会覆盖它,导致渐变消失。
请改为对外层容器应用动画。
## API 参考
### ScrollShadow
| Prop | 类型 | 默认值 | 描述 |
| -------------------- | ---------------------------------------------------------------------------------- | ------------ | ----------------- |
| `orientation` | `"vertical"` \| `"horizontal"` | `"vertical"` | 滚动方向 |
| `variant` | `"fade"` | `"fade"` | 阴影视觉效果样式 |
| `size` | `number` | `40` | 阴影渐变尺寸(像素) |
| `offset` | `number` | `0` | 开始显示阴影前的滚动偏移量(像素) |
| `hideScrollBar` | `boolean` | `false` | 是否隐藏原生滚动条 |
| `isEnabled` | `boolean` | `true` | 是否启用滚动阴影检测 |
| `visibility` | `"auto"` \| `"both"` \| `"top"` \| `"bottom"` \| `"left"` \| `"right"` \| `"none"` | `"auto"` | 受控的阴影可见性 |
| `onVisibilityChange` | `(visibility: ScrollShadowVisibility) => void` | - | 阴影可见性变化时调用的回调 |
| `className` | `string` | - | 应用到根元素上的额外 CSS 类 |
| `children` | `ReactNode` | - | 可滚动的子内容 |
## 相关组件