# ButtonGroup **Category**: react **URL**: https://heroui.com/en/docs/react/components/button-group **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(buttons)/button-group.mdx > Group related buttons together with consistent styling and spacing ## Usage ```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 (
{/* Single button with dropdown */}
All commits from this branch will be added to the base branch The 14 commits from this branch will be combined into one commit in the base branch The 14 commits from this branch will be rebased and added to the base branch
{/* Individual buttons */}
{/* Previous/Next Button Group */}
{/* Content Selection Button Group */}
{/* Text Alignment Button Group */}
{/* Icon-Only Alignment Button Group */}
); } ``` ## Anatomy ```tsx import { ButtonGroup, Button } from '@heroui/react'; export default () => ( ); ``` > **ButtonGroup** wraps multiple Button components together, applying consistent styling, spacing, and automatic border radius handling. It uses React Context to pass `size`, `variant`, and `isDisabled` props to all child buttons. ## Examples ### Variants ```tsx import {Button, ButtonGroup} from "@heroui/react"; export function Variants() { return (

Primary

Secondary

Tertiary

Outline

Ghost

Danger

); } ``` ### Sizes ```tsx import {Button, ButtonGroup} from "@heroui/react"; export function Sizes() { return (

Small

Medium (default)

Large

); } ``` ### Orientation Use the `orientation` prop to arrange buttons horizontally or vertically. ```tsx import {TextAlignCenter, TextAlignJustify, TextAlignLeft, TextAlignRight} from "@gravity-ui/icons"; import {Button, ButtonGroup} from "@heroui/react"; export function Orientation() { return (
Horizontal
Vertical
); } ``` ### With Icons ```tsx import {Globe, Plus, TrashBin} from "@gravity-ui/icons"; import {Button, ButtonGroup} from "@heroui/react"; export function WithIcons() { return (

With icons

Icon only buttons

); } ``` ### Full Width ```tsx import {TextAlignCenter, TextAlignLeft, TextAlignRight} from "@gravity-ui/icons"; import {Button, ButtonGroup} from "@heroui/react"; export function FullWidth() { return (
); } ``` ### Disabled State ```tsx import {Button, ButtonGroup} from "@heroui/react"; export function Disabled() { return (

All buttons disabled

Group disabled, but one button overrides

); } ``` ### Without Separator Simply omit the `` component from your buttons. ```tsx import {Button, ButtonGroup} from "@heroui/react"; export function WithoutSeparator() { return ( ); } ``` ## Customization ### Tailwind CSS ```tsx import {ChevronDown} from "@gravity-ui/icons"; import {Button, ButtonGroup, Description, Dropdown, Label} from "@heroui/react"; export function CustomStyles() { return ( All commits from this branch will be added to the base branch The 14 commits from this branch will be combined into one commit in the base branch The 14 commits from this branch will be rebased and added to the base branch ); } ``` ### Global CSS To customize the ButtonGroup component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ButtonGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/button-group.css)): #### Base Classes \[!toc] * `.button-group` - Base button group container * `.button-group--full-width` - Full width modifier * `.button-group__separator` - Separator element between buttons The ButtonGroup component automatically applies border radius to buttons: * First button gets rounded left/start edge * Last button gets rounded right/end edge * Middle buttons have no border radius * Single button gets full border radius on all edges Add `` inside each Button (except the first) to show dividers between buttons. ## API Reference ### ButtonGroup | Prop | Type | Default | Description | | ------------- | --------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | `variant` | `'primary' \| 'secondary' \| 'tertiary' \| 'ghost' \| 'danger'` | - | Visual style variant applied to all buttons in the group | | `size` | `'sm' \| 'md' \| 'lg'` | - | Size applied to all buttons in the group | | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | The orientation of the button group | | `fullWidth` | `boolean` | `false` | Whether the button group should take full width of its container | | `isDisabled` | `boolean` | `false` | Whether all buttons in the group are disabled (can be overridden on individual buttons) | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | Button components to group together | ### ButtonGroup.Separator | Prop | Type | Default | Description | | ----------- | -------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | ## Note * ButtonGroup uses React Context to pass `size`, `variant`, and `isDisabled` props to all child Button components * **Only direct child buttons receive the ButtonGroup props** - Buttons nested inside other components (like Modal, Dropdown, etc.) will not inherit the group's props even if they are descendants of the ButtonGroup * Individual Button components can override the group's `isDisabled` prop by setting `isDisabled={false}` * The component automatically handles border radius between buttons * Add `` inside each Button (except the first) to show dividers between buttons * Buttons in a group have their active/pressed scale transform removed for a more cohesive appearance ## Related Components ## 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/en/docs/react/components/button **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(buttons)/button.mdx > A clickable button component with multiple variants and states ## Usage ```tsx import { Button } from '@heroui/react'; ``` ```tsx "use client"; import {Button} from "@heroui/react"; export function Basic() { return ; } ``` ## Examples ### Variants ```tsx import {Button} from "@heroui/react"; export function Variants() { return (
); } ``` ### Sizes ```tsx import {Button} from "@heroui/react"; export function Sizes() { return (
); } ``` ### With Icons ```tsx import {Envelope, Globe, Plus, TrashBin} from "@gravity-ui/icons"; import {Button} from "@heroui/react"; export function WithIcons() { return (
); } ``` ### Icon Only ```tsx import {Ellipsis, Gear, TrashBin} from "@gravity-ui/icons"; import {Button} from "@heroui/react"; export function IconOnly() { return (
); } ``` ### Loading ```tsx "use client"; import {Button, Spinner} from "@heroui/react"; import React from "react"; export function Loading() { return ( ); } ``` ### Loading State ```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 ( ); } ``` ### Full Width ```tsx import {Plus} from "@gravity-ui/icons"; import {Button} from "@heroui/react"; export function FullWidth() { return (
); } ``` ### Disabled State ```tsx import {Button} from "@heroui/react"; export function Disabled() { return (
); } ``` ### Social Buttons ```tsx import {Button} from "@heroui/react"; import {Icon} from "@iconify/react"; export function Social() { return (
); } ``` ### Render Function ```tsx "use client"; import {Button} from "@heroui/react"; export function RenderFunction() { return ( ); } ``` ### Adding custom variants You can extend HeroUI components by wrapping them and adding your own custom variants. ```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 ); } ``` ## Customization ### Tailwind CSS ````tsx "use client"; import {Button} from "@heroui/react"; /** * The `gradient-border` class below depends on a global utility. * Add this to your global CSS (e.g. `src/app/globals.css`) before using it: * * ```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; * } * } * ``` * * On the component, set `--gradient-border` (gradient) and optionally * `--gradient-border-width` (default `1px`) via Tailwind arbitrary properties * or inline styles. */ export function CustomStyles() { return ( ); } ```` ### Global CSS To customize the Button component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 Button component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/button.css)): #### Base & Size Classes \[!toc] * `.button` - Base button styles * `.button--sm` - Small size variant * `.button--md` - Medium size variant * `.button--lg` - Large size variant #### Variant Classes \[!toc] * `.button--primary` * `.button--secondary` * `.button--tertiary` * `.button--outline` * `.button--ghost` * `.button--danger` * `.button--danger-soft` #### Modifier Classes \[!toc] * `.button--icon-only` * `.button--icon-only.button--sm` * `.button--icon-only.button--lg` ### Interactive States The button supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` * **Active/Pressed**: `:active` or `[data-pressed="true"]` (includes scale transform) * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring) * **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events) * **Pending**: `[data-pending]` (no pointer events during loading) ## API Reference ### Button | Prop | Type | Default | Description | | ------------ | --------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | | `variant` | `'primary' \| 'secondary' \| 'tertiary' \| 'outline' \| 'ghost' \| 'danger' \| 'danger-soft'` | `'primary'` | Visual style variant | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the button | | `fullWidth` | `boolean` | `false` | Whether the button should take full width of its container | | `isDisabled` | `boolean` | `false` | Whether the button is disabled | | `isPending` | `boolean` | `false` | Whether the button is in a loading state | | `isIconOnly` | `boolean` | `false` | Whether the button contains only an icon | | `onPress` | `(e: PressEvent) => void` | - | Handler called when the button is pressed | | `children` | `React.ReactNode \| (values: ButtonRenderProps) => React.ReactNode` | - | Button 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 | | ---------------- | --------- | ---------------------------------------------- | | `isPending` | `boolean` | Whether the button is in a loading state | | `isPressed` | `boolean` | Whether the button is currently pressed | | `isHovered` | `boolean` | Whether the button is hovered | | `isFocused` | `boolean` | Whether the button is focused | | `isFocusVisible` | `boolean` | Whether the button should show focus indicator | | `isDisabled` | `boolean` | Whether the button is disabled | ## Related Showcases ## Related Components ## 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/en/docs/react/components/close-button **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(buttons)/close-button.mdx > Button component for closing dialogs, modals, or dismissing content ## Usage ```tsx import { CloseButton } from "@heroui/react"; ``` ```tsx import {CloseButton} from "@heroui/react"; export function Default() { return ; } ``` ## Examples ### Interactive ```tsx "use client"; import {CloseButton} from "@heroui/react"; import {useState} from "react"; export function Interactive() { const [count, setCount] = useState(0); return (
setCount(count + 1)} /> Clicked: {count} times
); } ``` ### With Custom Icon ```tsx import {CircleXmark, Xmark} from "@gravity-ui/icons"; import {CloseButton} from "@heroui/react"; export function WithCustomIcon() { return (
Custom Icon
Alternative Icon
); } ``` ## Customization ### Tailwind CSS ```tsx import {CloseButton} from "@heroui/react"; export function CustomStyles() { return ( ); } ``` ### Global CSS To customize the CloseButton component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 CloseButton component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/close-button.css)): #### Base Classes \[!toc] * `.close-button` - Base component styles #### Variant Classes \[!toc] * `.close-button--default` - Default variant ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` * **Active/Pressed**: `:active` or `[data-pressed="true"]` * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` * **Disabled**: `:disabled` or `[aria-disabled="true"]` ## API Reference ### CloseButton | Prop | Type | Default | Description | | ------------ | ----------------------- | --------------- | ------------------------------------------- | | `variant` | `"default"` | `"default"` | Visual variant of the button | | `children` | `ReactNode \| function` | `` | Content to display (defaults to close icon) | | `onPress` | `() => void` | - | Handler called when the button is pressed | | `isDisabled` | `boolean` | `false` | Whether the button is disabled | ### React Aria Button Props CloseButton extends all React Aria Button props. Common props include: | Prop | Type | Description | | ------------------ | -------- | --------------------------------------- | | `aria-label` | `string` | Accessible label for screen readers | | `aria-labelledby` | `string` | ID of element that labels the button | | `aria-describedby` | `string` | ID of element that describes the button | ### Render Props When using the render prop pattern, these values are provided: | Prop | Type | Description | | ------------ | --------- | ------------------------------ | | `isHovered` | `boolean` | Whether the button is hovered | | `isPressed` | `boolean` | Whether the button is pressed | | `isFocused` | `boolean` | Whether the button is focused | | `isDisabled` | `boolean` | Whether the button is disabled | ## Related Components ## 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/en/docs/react/components/toggle-button-group **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(buttons)/toggle-button-group.mdx > Groups multiple ToggleButtons into a unified control, allowing users to select one or multiple options. ## Usage ```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 ( ); } ``` ## Anatomy ```tsx import { ToggleButtonGroup, ToggleButton } from '@heroui/react'; export default () => ( First Second Third ); ``` ## Examples ### Sizes ```tsx import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons"; import {ToggleButton, ToggleButtonGroup} from "@heroui/react"; export function Sizes() { return (
Small
Medium (default)
Large
); } ``` ### Orientation ```tsx import {Bold, Italic, Underline} from "@gravity-ui/icons"; import {ToggleButton, ToggleButtonGroup} from "@heroui/react"; export function Orientation() { return (
Horizontal
Vertical
); } ``` ### Full Width ```tsx import { Bold, Italic, Strikethrough, TextAlignCenter, TextAlignLeft, TextAlignRight, Underline, } from "@gravity-ui/icons"; import {ToggleButton, ToggleButtonGroup} from "@heroui/react"; export function FullWidth() { return (
Left Center Right
); } ``` ### Disabled ```tsx import {Bold, Italic, Underline} from "@gravity-ui/icons"; import {ToggleButton, ToggleButtonGroup} from "@heroui/react"; export function Disabled() { return (
All buttons disabled
Individual button disabled
); } ``` ### Without Separator Simply omit the `` component from your buttons. ```tsx import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons"; import {ToggleButton, ToggleButtonGroup} from "@heroui/react"; export function WithoutSeparator() { return ( ); } ``` ### Detached Use `isDetached` to separate buttons with gaps instead of connecting them. ```tsx import {Bold, Italic, Strikethrough, Underline} from "@gravity-ui/icons"; import {ToggleButton, ToggleButtonGroup} from "@heroui/react"; export function Attached() { return (
Attached (default)
Detached
); } ``` ### Selection Mode Use `selectionMode="single"` for mutually exclusive choices or `selectionMode="multiple"` for independent toggles. ```tsx import { Bold, Italic, Strikethrough, TextAlignCenter, TextAlignLeft, TextAlignRight, Underline, } from "@gravity-ui/icons"; import {ToggleButton, ToggleButtonGroup} from "@heroui/react"; export function SelectionMode() { return (
Single selection Left Center Right
Multiple selection
); } ``` ### Controlled ```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 (

Selected:{" "} {selectedKeys.size > 0 ? [...selectedKeys].join(", ") : "None"}

); } ``` ## Customization ### 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 ( ); } ``` ### Global CSS To customize the ToggleButtonGroup component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ToggleButtonGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/toggle-button-group.css)): #### Base & Layout Classes \[!toc] * `.toggle-button-group` - Base container styles * `.toggle-button-group--horizontal` - Horizontal orientation * `.toggle-button-group--vertical` - Vertical orientation * `.toggle-button-group--full-width` - Full width modifier * `.toggle-button-group__separator` - Separator element between buttons #### Modifier Classes \[!toc] * `.toggle-button-group--detached` - Detached mode (separated buttons with gaps) ## API Reference ### ToggleButtonGroup Inherits from [React Aria ToggleButtonGroup](https://react-aria.adobe.com/ToggleButtonGroup). | Prop | Type | Default | Description | | ------------------------ | ---------------------------- | -------------- | ------------------------------------------------ | | `selectionMode` | `"single" \| "multiple"` | `"single"` | Whether one or multiple buttons can be selected | | `selectedKeys` | `Iterable` | - | Controlled selection state | | `defaultSelectedKeys` | `Iterable` | - | Default selected keys (uncontrolled) | | `onSelectionChange` | `(keys: Set) => void` | - | Called when selection changes | | `disallowEmptySelection` | `boolean` | `false` | Prevents clearing all selections | | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction | | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size propagated to child ToggleButtons | | `isDetached` | `boolean` | `false` | Whether buttons are visually separated with gaps | | `fullWidth` | `boolean` | `false` | Whether the group fills available width | | `isDisabled` | `boolean` | `false` | Disables all buttons in the group | | `className` | `string` | - | Additional CSS classes | ### ToggleButtonGroup.Separator | Prop | Type | Default | Description | | ----------- | -------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | ## Note * ToggleButtonGroup uses React Context to pass `size` to all child ToggleButton components * Each ToggleButton must have a unique `id` prop that corresponds to the keys used in `selectedKeys` / `defaultSelectedKeys` * The `isDisabled` prop is handled natively by React Aria and disables all child ToggleButtons — individual buttons can override this by setting `isDisabled={false}` * The component automatically handles border radius between buttons * Add `` inside each ToggleButton (except the first) to show dividers between buttons * Use `disallowEmptySelection` with `selectionMode="single"` to ensure one option is always selected ## Related Components ## 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/en/docs/react/components/toggle-button **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(buttons)/toggle-button.mdx > An interactive toggle control for on/off or selected/unselected states ## Usage ```tsx import { ToggleButton } from '@heroui/react'; ``` ```tsx import {Heart} from "@gravity-ui/icons"; import {ToggleButton} from "@heroui/react"; export function Basic() { return ( Like ); } ``` ## Examples ### Variants ```tsx import {Heart} from "@gravity-ui/icons"; import {ToggleButton} from "@heroui/react"; export function Variants() { return (
Default Ghost
); } ``` ### Icon Only ```tsx import {Bookmark, Heart} from "@gravity-ui/icons"; import {ToggleButton} from "@heroui/react"; export function IconOnly() { return (
); } ``` ### Sizes ```tsx import {Heart} from "@gravity-ui/icons"; import {ToggleButton} from "@heroui/react"; export function Sizes() { return (
Small Medium Large
); } ``` ### Disabled ```tsx import {Heart, HeartFill} from "@gravity-ui/icons"; import {ToggleButton} from "@heroui/react"; export function Disabled() { return (
Like Like
); } ``` ### Controlled ```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 ? "Liked" : "Like"} )}

Status: {isSelected ? "Selected" : "Not selected"}

); } ``` ## Customization ### Tailwind CSS ```tsx import {Heart} from "@gravity-ui/icons"; import {ToggleButton} from "@heroui/react"; export function CustomStyles() { return ( Save article ); } ``` ### Global CSS To customize the ToggleButton component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ToggleButton component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/toggle-button.css)): #### Base & Size Classes \[!toc] * `.toggle-button` - Base toggle button styles * `.toggle-button--sm` - Small size variant * `.toggle-button--md` - Medium size variant (default) * `.toggle-button--lg` - Large size variant #### Variant Classes \[!toc] * `.toggle-button--default` - Default variant with filled background * `.toggle-button--ghost` - Ghost variant with transparent background #### Modifier Classes \[!toc] * `.toggle-button--icon-only` - Icon-only toggle button * `.toggle-button--icon-only.toggle-button--sm` - Small icon-only * `.toggle-button--icon-only.toggle-button--lg` - Large icon-only ### Interactive States The toggle button supports both CSS pseudo-classes and data attributes for flexibility: * **Selected**: `[data-selected="true"]` (accent background and foreground) * **Hover**: `:hover` or `[data-hovered="true"]` * **Active/Pressed**: `:active` or `[data-pressed="true"]` (includes scale transform) * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring) * **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events) ## API Reference ### ToggleButton Inherits from [React Aria ToggleButton](https://react-spectrum.adobe.com/react-aria/ToggleButton.html). | Prop | Type | Default | Description | | ----------------- | ------------------------------------------------------------------------- | ----------- | ----------------------------------------- | | `variant` | `'default' \| 'ghost'` | `'default'` | Visual style variant | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the toggle button | | `isIconOnly` | `boolean` | `false` | Whether the button contains only an icon | | `isSelected` | `boolean` | - | Controlled selected state | | `defaultSelected` | `boolean` | `false` | Default selected state (uncontrolled) | | `isDisabled` | `boolean` | `false` | Whether the toggle button is disabled | | `onChange` | `(isSelected: boolean) => void` | - | Handler called when selection changes | | `onPress` | `(e: PressEvent) => void` | - | Handler called when the button is pressed | | `children` | `React.ReactNode \| (values: ToggleButtonRenderProps) => React.ReactNode` | - | Button content or render prop | ### Render Props When using the render prop pattern, these values are provided: | Prop | Type | Description | | ---------------- | --------- | ---------------------------------------------- | | `isSelected` | `boolean` | Whether the button is currently selected | | `isPressed` | `boolean` | Whether the button is currently pressed | | `isHovered` | `boolean` | Whether the button is hovered | | `isFocused` | `boolean` | Whether the button is focused | | `isFocusVisible` | `boolean` | Whether the button should show focus indicator | | `isDisabled` | `boolean` | Whether the button is disabled | ## Related Components ## 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/en/docs/react/components/dropdown **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(collections)/dropdown.mdx > A dropdown displays a list of actions or options that a user can choose ## Usage ```tsx import { Dropdown } from '@heroui/react'; ``` ```tsx "use client"; import {Button, Dropdown, Label} from "@heroui/react"; export function Default() { return ( console.log(`Selected: ${key}`)}> ); } ``` ## Anatomy ```tsx import { Dropdown, Button, Label, Description, Header, Kbd, Separator } from '@heroui/react'; export default () => ( console.log(`Selected: ${key}`)}> N O S D ); } ``` ### With Descriptions ```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}`)}>
Create a new file
N
Open an existing file
O
Save the current file
S
Move to trash
D
); } ``` ### With Disabled Items ```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}`)} >
Actions
Create a new file
N
Make changes
E
Danger zone
Move to trash
D
); } ``` ### With Sections ```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}`)}>
Actions
Create a new file
N
Make changes
E
Danger zone
Move to trash
D
); } ``` ### With Multiple Selection ```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 (
Select a fruit
); } ``` ### Controlled ```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 (

Selected: {selectedItems.length > 0 ? selectedItems.join(", ") : "None"}

); } ``` ### Controlled Open State ```tsx "use client"; import {Button, Dropdown, Label} from "@heroui/react"; import {useState} from "react"; export function ControlledOpenState() { const [open, setOpen] = useState(false); return (

Dropdown is: {open ? "open" : "closed"}

); } ``` ### With Single Selection ```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 (
Select a fruit
); } ``` ### Single With Custom Indicator ```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 (
Select a fruit
{({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)} {({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)} {({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)}
{({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)} {({isSelected}) => (isSelected ? CustomCheckmarkIcon : null)}
); } ``` ### With Section Level Selection ```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 (
Actions
X C U
Text Style
B I U
Text Alignment
A H D
); } ``` ### With Keyboard Shortcuts ```tsx "use client"; import {Button, Dropdown, Kbd, Label} from "@heroui/react"; export function WithKeyboardShortcuts() { return ( console.log(`Selected: ${key}`)}> N O S D ); } ``` ### With Submenus ```tsx "use client"; import {Button, Dropdown, Label} from "@heroui/react"; export function WithSubmenus() { return ( console.log(`Selected: ${key}`)}> ); } ``` ### With Custom Submenu Indicator ```tsx "use client"; import {ArrowRight} from "@gravity-ui/icons"; import {Button, Dropdown, Label} from "@heroui/react"; export function WithCustomSubmenuIndicator() { return ( console.log(`Selected: ${key}`)}> ); } ``` ### Custom Trigger ```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

); } ``` ### Long Press Trigger ```tsx import {Button, Dropdown, Label} from "@heroui/react"; export function LongPressTrigger() { return ( ); } ``` ## Customization ### Tailwind CSS ```tsx import {Button, Dropdown, Label} from "@heroui/react"; export function CustomStyles() { return ( ); } ``` ### Global CSS To customize the Dropdown component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 Dropdown component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/dropdown.css)): #### Base Classes \[!toc] * `.dropdown` - Base dropdown container * `.dropdown__trigger` - The button or element that triggers the dropdown * `.dropdown__popover` - The popover container * `.dropdown__menu` - The menu container inside the popover #### State Classes \[!toc] * `.dropdown__trigger[data-focus-visible="true"]` - Focused trigger state * `.dropdown__trigger[data-disabled="true"]` - Disabled trigger state * `.dropdown__trigger[data-pressed="true"]` - Pressed trigger state * `.dropdown__popover[data-entering]` - Entering animation state * `.dropdown__popover[data-exiting]` - Exiting animation state * `.dropdown__menu[data-selection-mode="single"]` - Single selection mode * `.dropdown__menu[data-selection-mode="multiple"]` - Multiple selection mode ### Menu Component Classes The Dropdown component uses Menu, MenuItem, and MenuSection as base components. These classes are also available for customization: #### Menu Classes \[!toc] * `.menu` - Base menu container ([menu.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/menu.css)) * `[data-slot="separator"]` - Separator elements within the menu #### MenuItem Classes \[!toc] * `.menu-item` - Base menu item container ([menu-item.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/menu-item.css)) * `.menu-item__indicator` - Selection indicator (checkmark or dot) * `[data-slot="menu-item-indicator--checkmark"]` - Checkmark indicator SVG * `[data-slot="menu-item-indicator--dot"]` - Dot indicator SVG * `.menu-item__indicator--submenu` - Submenu indicator (chevron) * `.menu-item--default` - Default variant styling * `.menu-item--danger` - Danger variant styling #### MenuItem State Classes \[!toc] * `.menu-item[data-focus-visible="true"]` - Focused item state (keyboard focus) * `.menu-item[data-focus="true"]` - Focused item state * `.menu-item[data-pressed]` - Pressed item state * `.menu-item[data-hovered]` - Hovered item state * `.menu-item[data-selected="true"]` - Selected item state * `.menu-item[data-disabled]` - Disabled item state * `.menu-item[data-has-submenu="true"]` - Item with submenu * `.menu-item[data-selection-mode="single"]` - Single selection mode * `.menu-item[data-selection-mode="multiple"]` - Multiple selection mode * `.menu-item[aria-checked="true"]` - Checked item (ARIA) * `.menu-item[aria-selected="true"]` - Selected item (ARIA) #### MenuSection Classes \[!toc] * `.menu-section` - Base menu section container ([menu-section.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/menu-section.css)) ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` on trigger and items * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on trigger and items * **Disabled**: `:disabled` or `[data-disabled="true"]` on trigger and items * **Pressed**: `:active` or `[data-pressed="true"]` on trigger and items * **Selected**: `[data-selected="true"]` or `[aria-selected="true"]` on items ## API Reference ### Dropdown | Prop | Type | Default | Description | | -------------- | --------------------------- | --------- | ------------------------------------------------------ | | `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 | | `trigger` | `"press" \| "longPress"` | `"press"` | The type of interaction that triggers the menu | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Dropdown content | ### Dropdown.Trigger | Prop | Type | Default | Description | | ----------- | ----------------------------- | ------- | ---------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Trigger content or render function | All [Button](https://react-spectrum.adobe.com/react-aria/Button.html) props are also supported when using a Button as the trigger. ### Dropdown.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 | All [Popover](https://react-spectrum.adobe.com/react-aria/Popover.html) props are also supported. ### Dropdown.Menu | Prop | Type | Default | Description | | --------------------- | ---------------------------------- | -------- | ----------------------------------------------- | | `selectionMode` | `"single" \| "multiple" \| "none"` | `"none"` | Whether single or multiple selection is enabled | | `selectedKeys` | `Iterable` | - | The currently selected keys (controlled) | | `defaultSelectedKeys` | `Iterable` | - | The initial selected keys (uncontrolled) | | `onSelectionChange` | `(keys: Selection) => void` | - | Handler called when the selection changes | | `disabledKeys` | `Iterable` | - | Keys of disabled items | | `onAction` | `(key: Key) => void` | - | Handler called when an item is activated | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Menu content | All [Menu](https://react-spectrum.adobe.com/react-aria/Menu.html#menu) props are also supported. ### Dropdown.Section | Prop | Type | Default | Description | | --------------------- | --------------------------- | ------- | -------------------------------------------- | | `selectionMode` | `"single" \| "multiple"` | - | Selection mode for items within this section | | `selectedKeys` | `Iterable` | - | The currently selected keys (controlled) | | `defaultSelectedKeys` | `Iterable` | - | The initial selected keys (uncontrolled) | | `onSelectionChange` | `(keys: Selection) => void` | - | Handler called when the selection changes | | `disabledKeys` | `Iterable` | - | Keys of disabled items | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Section content | All [MenuSection](https://react-spectrum.adobe.com/react-aria/Menu.html#menusection) props are also supported. ### Dropdown.Item | Prop | Type | Default | Description | | ----------- | ----------------------------- | ----------- | -------------------------------------- | | `id` | `Key` | - | Unique identifier for the item | | `textValue` | `string` | - | Text content of the item for typeahead | | `variant` | `"default" \| "danger"` | `"default"` | Visual variant of the item | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Item content or render function | All [MenuItem](https://react-spectrum.adobe.com/react-aria/Menu.html#menuitem) props are also supported. ### Dropdown.ItemIndicator | Prop | Type | Default | Description | | ----------- | ----------------------------- | ------------- | ------------------------------------------- | | `type` | `"checkmark" \| "dot"` | `"checkmark"` | Type of indicator to display | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Custom indicator content or render function | When using a render function, these values are provided: | Prop | Type | Description | | ----------------- | --------- | --------------------------------------------- | | `isSelected` | `boolean` | Whether the item is selected | | `isIndeterminate` | `boolean` | Whether the item is in an indeterminate state | ### Dropdown.SubmenuIndicator | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------------------ | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Custom indicator content | ### Dropdown.SubmenuTrigger | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ----------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Submenu trigger content | All [SubmenuTrigger](https://react-spectrum.adobe.com/react-aria/Menu.html#submenutrigger) props are also supported. ### Render Props When using render functions with Dropdown.Item, these values are provided: | Prop | Type | Description | | ------------ | --------- | --------------------------------- | | `isSelected` | `boolean` | Whether the item is selected | | `isFocused` | `boolean` | Whether the item is focused | | `isDisabled` | `boolean` | Whether the item is disabled | | `isPressed` | `boolean` | Whether the item is being pressed | ## Accessibility The Dropdown component implements the ARIA menu pattern and provides: * Full keyboard navigation support (arrow keys, home/end, typeahead) * Screen reader announcements for actions and selection changes * Proper focus management * Support for disabled states * Long press interaction support * Submenu navigation For more information, see the [React Aria Menu documentation](https://react-spectrum.adobe.com/react-aria/Menu.html#menu). ## Related Components ## 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/en/docs/react/components/list-box **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(collections)/list-box.mdx > A listbox displays a list of options and allows a user to select one or more of them ## Usage ```tsx import { ListBox } from '@heroui/react'; ``` ```tsx import {Avatar, Description, Label, ListBox} from "@heroui/react"; export function Default() { return ( B
bob@heroui.com
F
fred@heroui.com
M
martha@heroui.com
); } ``` ## Anatomy ```tsx import { ListBox, Label, Description, Header } from '@heroui/react'; export default () => (
) ``` ## Examples ### With Disabled Items ```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(`Selected item: ${key}`)} >
Actions
Create a new file
N
Make changes
E
Danger zone
Move to trash
D
); } ``` ### With Sections ```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(`Selected item: ${key}`)} >
Actions
Create a new file
N
Make changes
E
Danger zone
Move to trash
D
); } ``` ### Multi Select ```tsx import {Avatar, Description, Label, ListBox, Surface} from "@heroui/react"; export function MultiSelect() { return ( B
bob@heroui.com
F
fred@heroui.com
M
martha@heroui.com
); } ``` ### Controlled ```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@heroui.com
{({isSelected}) => isSelected ? : null }
F
fred@heroui.com
{({isSelected}) => isSelected ? : null }
M
martha@heroui.com
{({isSelected}) => isSelected ? : null }

Selected: {selectedItems.length > 0 ? selectedItems.join(", ") : "None"}

); } ``` ### Virtualization ListBox 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 {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.email}
)}
); } ``` ### Custom Check Icon ```tsx "use client"; import {Check} from "@gravity-ui/icons"; import {Avatar, Description, Label, ListBox, Surface} from "@heroui/react"; export function CustomCheckIcon() { return ( B
bob@heroui.com
{({isSelected}) => isSelected ? : null }
F
fred@heroui.com
{({isSelected}) => isSelected ? : null }
M
martha@heroui.com
{({isSelected}) => isSelected ? : null }
); } ``` ### Render Function ```tsx "use client"; import {Avatar, Description, Label, ListBox} from "@heroui/react"; export function RenderFunction() { return (
} selectionMode="single" > } textValue="Bob" > B
bob@heroui.com
} textValue="Fred" > F
fred@heroui.com
} textValue="Martha" > M
martha@heroui.com
); } ``` ## Customization ### Tailwind CSS ```tsx import {Avatar, Description, Label, ListBox} from "@heroui/react"; export function CustomStyles() { return ( B
bob@heroui.com
F
fred@heroui.com
); } ``` ### Global CSS To customize the ListBox component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ListBox component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/list-box.css)): #### Base Classes \[!toc] * `.list-box` - Base listbox container * `.list-box-item` - Individual listbox item * `.list-box-item__indicator` - Selection indicator icon * `.list-box-section` - Section container for grouping items #### Variant Classes \[!toc] * `.list-box--default` - Default variant styling * `.list-box--danger` - Danger variant styling * `.list-box-item--default` - Default item variant * `.list-box-item--danger` - Danger item variant #### State Classes \[!toc] * `.list-box-item[data-selected="true"]` - Selected item state * `.list-box-item[data-focus-visible="true"]` - Focused item state * `.list-box-item[data-disabled="true"]` - Disabled item state * `.list-box-item__indicator[data-visible="true"]` - Visible indicator state ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` on item * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on item * **Selected**: `[data-selected="true"]` on item * **Disabled**: `:disabled` or `[data-disabled="true"]` on item ## API Reference ### ListBox | Prop | Type | Default | Description | | --------------------- | -------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | | `aria-label` | `string` | - | Accessibility label for the listbox | | `aria-labelledby` | `string` | - | ID of element that labels the listbox | | `selectionMode` | `"none" \| "single" \| "multiple"` | `"single"` | Selection behavior | | `selectedKeys` | `Selection` | - | Controlled selected keys | | `defaultSelectedKeys` | `Selection` | - | Initial selected keys | | `onSelectionChange` | `(keys: Selection) => void` | - | Handler called when selection changes | | `disabledKeys` | `Iterable` | - | Keys of disabled items | | `onAction` | `(key: Key) => void` | - | Handler called when an item is activated | | `variant` | `"default" \| "danger"` | `"default"` | Visual variant | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | ListBox items and sections | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### ListBox.Item | Prop | Type | Default | Description | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | | `id` | `Key` | - | Unique identifier for the item | | `textValue` | `string` | - | Text value for accessibility and typeahead | | `isDisabled` | `boolean` | `false` | Whether this item is disabled | | `variant` | `"default" \| "danger"` | `"default"` | Visual variant | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Item content or render function | | `render` | `(props: DetailedHTMLProps \| React.JSX.IntrinsicElements[keyof React.JSX.IntrinsicElements], renderProps: ListBoxItemRenderProps) => ReactElement` | - | Overrides the default DOM element with a custom render function. | ### ListBox.ItemIndicator | Prop | Type | Default | Description | | ----------- | ----------------------------- | ------- | ------------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Custom indicator content or render function | ### ListBox.Section | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ------------------------------------------ | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Section content including Header and Items | ### Render Props When using render functions with ListBox.Item or ListBox.ItemIndicator, these values are provided: | Prop | Type | Description | | ------------ | --------- | --------------------------------- | | `isSelected` | `boolean` | Whether the item is selected | | `isFocused` | `boolean` | Whether the item is focused | | `isDisabled` | `boolean` | Whether the item is disabled | | `isPressed` | `boolean` | Whether the item is being pressed | ### ListLayout | 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. | ## Accessibility The ListBox 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 For more information, see the [React Aria ListBox documentation](https://react-spectrum.adobe.com/react-aria/ListBox.html). ## Related Components ## 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/en/docs/react/components/tag-group **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(collections)/tag-group.mdx > A focusable list of tags with support for keyboard navigation, selection, and removal ## Usage ```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 ( News Travel Gaming Shopping ); } ``` ## Anatomy ```tsx import { TagGroup, Tag, Label, Description, ErrorMessage } from '@heroui/react'; export default () => ( ) ``` ## Examples ### Sizes ```tsx "use client"; import {Label, Tag, TagGroup} from "@heroui/react"; export function TagGroupSizes() { return (
News Travel Gaming News Travel Gaming News Travel Gaming
); } ``` ### Variants ```tsx "use client"; import {Label, Tag, TagGroup} from "@heroui/react"; export function TagGroupVariants() { return (
News Travel Gaming News Travel Gaming
); } ``` ### Disabled ```tsx "use client"; import {Description, Label, Tag, TagGroup} from "@heroui/react"; export function TagGroupDisabled() { return (
News Travel Gaming Some tags are disabled News Travel Gaming Tags disabled via disabledKeys prop
); } ``` ### Selection Modes ```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)} > News Travel Gaming Shopping Choose one category setMultipleSelected(keys)} > News Travel Gaming Shopping Choose multiple categories
); } ``` ### Controlled ```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)} > News Travel Gaming Shopping Selected: {Array.from(selected).length > 0 ? Array.from(selected).join(", ") : "None"}
); } ``` ### With Error Message ```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)} > Laundry Fitness center Parking Swimming pool Breakfast {isInvalid ? "Select at least one category" : "Selected: " + Array.from(selected).join(", ")} {!!isInvalid && <>Please select at least one category} ); } ``` ### With List Data ```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)} > No team members} > {(user) => ( {user.fallback} {user.name} )} Select team members for your project {list.selectedKeys !== "all" && Array.from(list.selectedKeys).length > 0 && (

Selected:

{Array.from(list.selectedKeys).map((key) => { const user = list.getItem(key); if (!user) return null; return (
{user.fallback} {user.name}
); })}
)}
); } ``` ### With Prefix ```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 Tags with icons F Fred M Michael J Jane Tags with avatars
); } ``` ### With Remove Button ```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: "News"}, {id: "travel", name: "Travel"}, {id: "gaming", name: "Gaming"}, {id: "shopping", name: "Shopping"}, ]); 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 (
No categories found} > {(tag) => ( {tag.name} )} Click the X to remove tags
No frameworks found} > {(tag) => ( {(renderProps) => ( <> {tag.name} {!!renderProps.allowsRemoving && ( )} )} )} Custom remove button with icon
); } ``` ### Render Function ```tsx "use client"; import {PlanetEarth, Rocket, ShoppingBag, SquareArticle} from "@gravity-ui/icons"; import {Tag, TagGroup} from "@heroui/react"; export function RenderFunction() { return (
} selectionMode="single" > News Travel Gaming Shopping ); } ``` ## Customization ### 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 ( News Travel Gaming Shopping ); } ``` ### Global CSS To customize the TagGroup component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 TagGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tag-group.css) and [tag.css](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/tag.css)): #### Base Classes \[!toc] * `.tag-group` - Base tag group container * `.tag-group__list` - Container for the list of tags * `.tag` - Base tag styles * `.tag__remove-button` - Remove button trigger #### Slot Classes \[!toc] * `.tag-group [slot="description"]` - Description slot styles * `.tag-group [slot="errorMessage"]` - ErrorMessage slot styles #### Size Classes \[!toc] * `.tag--sm` - Small size tag * `.tag--md` - Medium size tag (default) * `.tag--lg` - Large size tag #### Variant Classes \[!toc] * `.tag--default` - Default variant * `.tag--surface` - Surface variant with surface background #### State Classes \[!toc] * `.tag[data-selected="true"]` - Selected tag state * `.tag[data-disabled="true"]` - Disabled tag state * `.tag[data-hovered="true"]` - Hovered tag state * `.tag[data-pressed="true"]` - Pressed tag state * `.tag[data-focus-visible="true"]` - Focused tag state (keyboard focus) ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` on tag * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on tag * **Pressed**: `:active` or `[data-pressed="true"]` on tag * **Selected**: `[data-selected="true"]` or `[aria-selected="true"]` on tag * **Disabled**: `:disabled` or `[data-disabled="true"]` on tag ## API Reference ### TagGroup | Prop | Type | Default | Description | | --------------------- | ----------------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | | `selectionMode` | `"none" \| "single" \| "multiple"` | `"none"` | The type of selection that is allowed | | `selectedKeys` | `Selection` | - | The currently selected keys (controlled) | | `defaultSelectedKeys` | `Selection` | - | The initial selected keys (uncontrolled) | | `onSelectionChange` | `(keys: Selection) => void` | - | Handler called when the selection changes | | `disabledKeys` | `Iterable` | - | Keys of disabled tags | | `isDisabled` | `boolean` | - | Whether the tag group is disabled | | `onRemove` | `(keys: Set) => void` | - | Handler called when tags are removed | | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the tags in the group | | `variant` | `"default" \| "surface"` | `"default"` | Visual variant of the tags | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | TagGroup content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### TagGroup.List | Prop | Type | Default | Description | | ------------------ | -------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- | | `items` | `Iterable` | - | The items to display in the tag list | | `renderEmptyState` | `() => ReactNode` | - | Function to render when the list is empty | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | TagList content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### Tag | Prop | Type | Default | Description | | ------------ | ---------------------------------------------------------------------- | ------- | -------------------------------------------------------------------- | | `id` | `Key` | - | The unique identifier for the tag | | `textValue` | `string` | - | A string representation of the tag's content, used for accessibility | | `isDisabled` | `boolean` | - | Whether the tag is disabled | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Tag content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | **Note**: `size`, `variant` are inherited from the parent `TagGroup` component and cannot be set directly on individual `Tag` components. ### Tag.RemoveButton | Prop | Type | Default | Description | | ----------- | ----------- | ------- | ----------------------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode` | - | Custom remove button content (defaults to close icon) | **Note**: The `Tag.RemoveButton` component supports customization similar to `SearchField.ClearButton`. When `onRemove` is provided to `TagGroup`: * **Auto-rendering**: If no custom `Tag.RemoveButton` is included in the `Tag` children, a default remove button is automatically rendered. * **Custom button**: If a custom `Tag.RemoveButton` is provided as a child of `Tag`, it will be used instead of the auto-rendered button. * **Custom icon**: You can pass custom content (like icons) to `Tag.RemoveButton` children to customize the appearance. **Example - Auto-rendered (default)**: ```tsx News {/* Remove button is automatically rendered */} ``` **Example - Custom RemoveButton with icon**: ```tsx News ``` **Example - Custom RemoveButton in render props**: ```tsx {(renderProps) => ( <> News {!!renderProps.allowsRemoving && ( )} )} ``` ### Render Props When using render functions with TagGroup.List, these values are provided: | Prop | Type | Description | | ---------------- | --------- | ---------------------------------- | | `isSelected` | `boolean` | Whether the tag is selected | | `isDisabled` | `boolean` | Whether the tag is disabled | | `isHovered` | `boolean` | Whether the tag is hovered | | `isPressed` | `boolean` | Whether the tag is pressed | | `isFocused` | `boolean` | Whether the tag is focused | | `isFocusVisible` | `boolean` | Whether the tag has keyboard focus | ## Related Components ## 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/en/docs/react/components/color-area **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(colors)/color-area.mdx > A 2D color picker that allows users to select colors from a gradient area ## Usage ```tsx import { ColorArea } from '@heroui/react'; ``` ```tsx import {ColorArea} from "@heroui/react"; export function ColorAreaBasic() { return ( ); } ``` ## Anatomy ```tsx import { ColorArea } from '@heroui/react'; export default () => ( ); ``` ## Examples ### With Dots ```tsx import {ColorArea} from "@heroui/react"; export function ColorAreaWithDots() { return ( ); } ``` ### Color Space & Channels Use `colorSpace` to set the color space (RGB, HSL, HSB) and `xChannel`/`yChannel` props to customize which color channels are displayed on each axis. ```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 */} {/* X Channel Select */} {/* Y Channel Select */}
{/* Color Area */} {/* Color Value Display */}
{color.toString(colorSpace)}
); } ``` ### Disabled ```tsx import {ColorArea} from "@heroui/react"; export function ColorAreaDisabled() { return ( ); } ``` ### Controlled ```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)"}

); } ``` ### Render Function ```tsx "use client"; import {ColorArea} from "@heroui/react"; export function RenderFunction() { return (
} >
} /> ); } ``` ## Customization ### Tailwind CSS ```tsx import {ColorArea} from "@heroui/react"; export function CustomStyles() { return ( ); } ``` ### Global CSS To customize the ColorArea component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ColorArea component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-area.css)): #### Base Classes \[!toc] * `.color-area` - Base styles with gradient background and inner shadow * `.color-area--show-dots` - Adds dot grid overlay for precision picking #### Element Classes \[!toc] * `.color-area__thumb` - Draggable thumb indicator ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Disabled**: `[data-disabled="true"]` * **Focus**: `[data-focus-visible="true"]` * **Dragging**: `[data-dragging="true"]` (thumb only) ## API Reference ### ColorArea Inherits from [React Aria ColorArea](https://react-spectrum.adobe.com/react-aria/ColorArea.html). | Prop | Type | Default | Description | | -------------- | ---------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------- | | `value` | `string \| Color` | - | The current color value (controlled) | | `defaultValue` | `string \| Color` | - | The default color value (uncontrolled) | | `onChange` | `(color: Color) => void` | - | Handler called when the color changes while dragging | | `onChangeEnd` | `(color: Color) => void` | - | Handler called when the user stops dragging | | `xChannel` | `ColorChannel` | `"saturation"` | Color channel for the horizontal axis | | `yChannel` | `ColorChannel` | `"brightness"` | Color channel for the vertical axis | | `colorSpace` | `ColorSpace` | - | The color space for the channels | | `isDisabled` | `boolean` | `false` | Whether the color area is disabled | | `showDots` | `boolean` | `false` | Whether to show the dot grid overlay | | `className` | `string` | - | Additional CSS classes | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### ColorArea.Thumb | Prop | Type | Default | Description | | ----------- | ----------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `style` | `CSSProperties \| ((renderProps) => CSSProperties)` | - | Inline styles or render props function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ## Related Components ## 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/en/docs/react/components/color-field **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(colors)/color-field.mdx > Color input field with labels, descriptions, and validation built on React Aria ColorField ## Usage ```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 ( ); } ``` ## Anatomy ```tsx import {ColorField, Label, ColorSwatch, Description, FieldError, parseColor} from '@heroui/react'; export default () => ( ) ``` > **ColorField** combines label, color input, description, and error into a single accessible component. ## Examples ### Variants The ColorField.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 import {ColorField, Label} from "@heroui/react"; export function Variants() { return (
); } ``` ### On Surface When used inside a [Surface](/docs/react/components/surface) component, use `variant="secondary"` on ColorField.Group to apply the lower emphasis variant suitable for surface backgrounds. ```tsx import {ColorField, Description, Label, Surface} from "@heroui/react"; export function OnSurface() { return ( Select your theme color ); } ``` ### With Description ```tsx import {ColorField, Description, Label} from "@heroui/react"; export function WithDescription() { return (
Enter your brand's primary color Used for highlights and CTAs
); } ``` ### Required Field ```tsx import {ColorField, Description, Label} from "@heroui/react"; export function Required() { return (
Required field
); } ``` ### Disabled State ```tsx "use client"; import {ColorField, Description, Label} from "@heroui/react"; export function Disabled() { return (
This color field is disabled This color field is disabled
); } ``` ### Full Width ```tsx import {ColorField, Label} from "@heroui/react"; export function FullWidth() { return (
); } ``` ### Validation Use `isInvalid` together with `FieldError` to surface validation messages. ```tsx import {ColorField, FieldError, Label} from "@heroui/react"; export function Invalid() { return (
Please enter a valid hex color Invalid color format. Use hex (e.g., #FF5733)
); } ``` ### Channel Editing ColorField supports editing individual color channels (hue, saturation, lightness, red, green, blue, alpha) by setting the `colorSpace` and `channel` props. ```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 (

Edit individual HSL channels:

% %
Current: {color ? color.toString("hex") : "(empty)"}
); } ``` ### Controlled Control the value to synchronize with other components or state management. ```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 (
Current value: {value ? value.toString("hex") : "(empty)"}
); } ``` ### Form Example Complete form example with validation and submission handling. ```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 submitted:", {color: value.toString("hex")}); setValue(null); setIsSubmitting(false); }, 1500); }; return (
Choose your brand's primary color
); } ``` ### Render Function ```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} >
}> ); } ``` ## Customization ### 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 ( Applied to buttons, links, and focus rings. ); } ``` ### Global CSS ColorField has minimal default styling. Override the `.color-field` class to customize the container styling. ```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; } } } ``` ## Styling Reference HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes * `.color-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. ColorField.Group styling is documented below in the API Reference section. ### Interactive States ColorField 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 ### ColorField ColorField inherits all props from React Aria's [ColorField](https://react-aria.adobe.com/ColorField.md) component. #### Base Props | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------- | | `children` | `React.ReactNode \| (values: ColorFieldRenderProps) => React.ReactNode` | - | Child components (Label, ColorField.Group, etc.) or render function. | | `className` | `string \| (values: ColorFieldRenderProps) => string` | - | CSS classes for styling, supports render props. | | `style` | `React.CSSProperties \| (values: ColorFieldRenderProps) => React.CSSProperties` | - | Inline styles, supports render props. | | `fullWidth` | `boolean` | `false` | Whether the color 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` | `Color \| null` | - | Current value (controlled). | | `defaultValue` | `Color \| null` | - | Default value (uncontrolled). | | `onChange` | `(color: Color \| null) => void` | - | Handler called when the value changes. | #### Channel Props | Prop | Type | Default | Description | | ------------ | -------------- | ------- | ---------------------------------------------------------------------------- | | `colorSpace` | `ColorSpace` | - | The color space that the color field operates in when `channel` is provided. | | `channel` | `ColorChannel` | - | The color channel to edit. If not provided, edits hex value. | #### 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: Color) => ValidationError \| true \| null \| undefined` | - | Custom validation function. | | `validationBehavior` | `'native' \| 'aria'` | `'native'` | Whether to use native HTML form validation or ARIA attributes. | #### State Props | Prop | Type | Default | Description | | ----------------- | --------- | ------- | -------------------------------------------------- | | `isDisabled` | `boolean` | - | Whether the input is disabled. | | `isReadOnly` | `boolean` | - | Whether the input can be selected but not changed. | | `isWheelDisabled` | `boolean` | - | Whether to disable changing the value with scroll. | #### 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 ColorField works with these separate components that should be imported and used directly: * **Label** - Field label component from `@heroui/react` * **ColorField.Group** - Color input group component (documented below) * **ColorField.Input** - Input element within ColorField.Group * **ColorField.Prefix** / **ColorField.Suffix** - Prefix and suffix slots for the input group * **ColorSwatch** - Color preview 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 ColorField for composition: ```tsx import {ColorField, Label, ColorSwatch, Description, FieldError, parseColor} from '@heroui/react'; Select your brand's primary color. Please enter a valid color. ``` ### Color Types ColorField uses `Color` objects from React Aria Components: ```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 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). | ### ColorField.Group ColorField.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 color 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. | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### ColorField.Input ColorField.Input accepts all props from React Aria's `Input` component plus the following: | Prop | Type | Default | Description | | ------------- | -------- | ------- | -------------------------------------------------- | | `className` | `string` | - | Tailwind classes merged with the component styles. | | `placeholder` | `string` | - | Placeholder text shown when empty. | ### ColorField.Prefix ColorField.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. | ### ColorField.Suffix ColorField.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. | ## ColorField.Group Styling ### Customizing the component classes The base classes power every instance. Override them once with `@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` – Root container styling * `.color-input-group__input` – Input wrapper styling * `.color-input-group__prefix` – Prefix element styling * `.color-input-group__suffix` – Suffix element styling ### ColorField.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"]` ## Related Components ## 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/en/docs/react/components/color-picker **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(colors)/color-picker.mdx > A composable color picker that synchronizes color value between multiple color components ## Usage ```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 ( ); } ``` ## Anatomy The ColorPicker is a composable component that combines multiple color components: ```tsx import { ColorPicker, ColorArea, ColorSlider, ColorSwatch, Label } from '@heroui/react'; export default () => ( ); ``` ## Examples ### Controlled ```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) => ( ))}

Selected: {color.toString("hex")}

); } ``` ### With Swatches ```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) => ( ))} ); } ``` ### With Fields Use `ColorField` to allow users to edit individual color channel values with a `Select` to switch between color spaces. ```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"; export function WithFields() { const [colorSpace, setColorSpace] = useState("hsl"); const colorChannelsByColorSpace: Record = { hsb: ["hue", "saturation", "brightness"], hsl: ["hue", "saturation", "lightness"], rgb: ["red", "green", "blue"], }; return (
{colorChannelsByColorSpace[colorSpace].map((channel) => ( ))}
); } ``` ### With Sliders Use multiple `ColorSlider` components to adjust each channel of a color value. ```tsx "use client"; import type {ColorChannel, ColorSpace} from "@heroui/react"; import {ColorPicker, ColorSlider, ColorSwatch, Label, ListBox, Select} from "@heroui/react"; import {useState} from "react"; 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 (
{colorChannelsByColorSpace[colorSpace].map((channel: ColorChannel) => ( // @ts-expect-error - TypeScript can't correlate dynamic colorSpace with channel type ))}
); } ``` ## Customization ### Tailwind CSS ```tsx import {ColorArea, ColorPicker, ColorSlider, ColorSwatch, Label} from "@heroui/react"; export function CustomStyles() { return ( ); } ``` ### Global CSS To customize the ColorPicker component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ColorPicker component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-picker.css)): #### Base Classes \[!toc] * `.color-picker` - Base container * `.color-picker__trigger` - Trigger button * `.color-picker__popover` - Popover container ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` * **Disabled**: `:disabled` or `[data-disabled="true"]` ## API Reference ### ColorPicker Inherits from [React Aria ColorPicker](https://react-spectrum.adobe.com/react-aria/ColorPicker.html). | Prop | Type | Default | Description | | -------------- | ------------------------ | ------- | ---------------------------------------------------- | | `value` | `string \| Color` | - | The current color value (controlled) | | `defaultValue` | `string \| Color` | - | The default color value (uncontrolled) | | `onChange` | `(color: Color) => void` | - | Handler called when the color changes | | `children` | `React.ReactNode` | - | Content of the color picker (Trigger, Popover, etc.) | | `className` | `string` | - | Additional CSS classes | ### ColorPicker.Trigger | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------- | ------- | ------------------------------ | | `children` | `React.ReactNode \| ((renderProps) => React.ReactNode)` | - | Trigger content or render prop | | `className` | `string` | - | Additional CSS classes | ### ColorPicker.Popover | Prop | Type | Default | Description | | ----------- | ----------------- | --------------- | ------------------------ | | `placement` | `Placement` | `"bottom left"` | Placement of the popover | | `children` | `React.ReactNode` | - | Popover content | | `className` | `string` | - | Additional CSS classes | ### Related Types #### Color Represents a color value. See [React Aria Color](https://react-spectrum.adobe.com/react-aria/ColorPicker.html#color) for full API. | Method | Description | | ---------------------------------- | ---------------------------------------------------------------------------- | | `toString(format)` | Converts the color to a string in the given format (hex, rgb, hsl, hsb, css) | | `toFormat(format)` | Converts the color to the given format and returns a new Color object | | `getChannelValue(channel)` | Returns the numeric value for a given channel | | `withChannelValue(channel, value)` | Sets the numeric value for a channel and returns a new 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 ## 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/en/docs/react/components/color-slider **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(colors)/color-slider.mdx > A color slider allows users to adjust an individual channel of a color value ## Usage ```tsx import { ColorSlider, Label } from '@heroui/react'; ``` ```tsx import {ColorSlider, Label} from "@heroui/react"; export function Basic() { return ( ); } ``` ## Anatomy ```tsx import { ColorSlider, Label } from '@heroui/react'; export default () => ( ) ``` ## Examples ### Disabled ```tsx import {ColorSlider, Label} from "@heroui/react"; export function Disabled() { return ( ); } ``` ### Vertical ```tsx import {ColorSlider} from "@heroui/react"; export function Vertical() { return (
); } ``` ### Controlled ```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 (

Current color: {color.toString("hsl")}

); } ``` ### HSL Channels Use multiple ColorSliders to control different channels of a color value. The sliders can share the same color value to create a complete color picker. ```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 (

Current color: {color.toString("hsl")}

); } ``` ### Alpha Channel The alpha channel slider shows a transparency checkerboard pattern to help visualize the transparency level. ```tsx import {ColorSlider, Label} from "@heroui/react"; export function AlphaChannel() { return ( ); } ``` ### RGB Channels You can also use RGB color space with red, green, and blue channels. ```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 (

Current color: {color.toString("rgb")}

); } ``` ### Render Function ```tsx "use client"; import {ColorSlider, Label} from "@heroui/react"; export function RenderFunction() { return (
} > ); } ``` ## Customization ### Tailwind CSS ```tsx import {ColorSlider, Label} from "@heroui/react"; export function CustomStyles() { return ( ); } ``` ### Global CSS To customize the ColorSlider component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ColorSlider component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-slider.css)): #### Base Classes \[!toc] * `.color-slider` - Base slider container * `.color-slider__output` - Output element displaying current value * `.color-slider__track` - Track element with color gradient * `.color-slider__thumb` - Thumb element showing current color #### State Classes \[!toc] * `.color-slider[data-disabled="true"]` - Disabled slider state * `.color-slider[data-orientation="vertical"]` - Vertical orientation * `.color-slider__thumb[data-dragging="true"]` - Thumb being dragged * `.color-slider__thumb[data-focus-visible="true"]` - Thumb keyboard focused * `.color-slider__thumb[data-disabled="true"]` - Disabled thumb state ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` on thumb * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on thumb * **Dragging**: `[data-dragging="true"]` on thumb * **Disabled**: `:disabled` or `[data-disabled="true"]` on slider or thumb ## API Reference ### ColorSlider Inherits from [React Aria ColorSlider](https://react-spectrum.adobe.com/react-aria/ColorSlider.html). | Prop | Type | Default | Description | | -------------- | ------------------------------------------------------------------------------ | -------------- | --------------------------------------------------------------------------------------------------------------- | | `channel` | `ColorChannel` | - | The color channel that the slider manipulates (hue, saturation, lightness, brightness, alpha, red, green, blue) | | `colorSpace` | `ColorSpace` | - | The color space (hsl, hsb, rgb). Defaults to the color space of the value | | `value` | `string \| Color` | - | The current color value (controlled) | | `defaultValue` | `string \| Color` | - | The default color value (uncontrolled) | | `onChange` | `(value: Color) => void` | - | Handler called when the value changes during dragging | | `onChangeEnd` | `(value: Color) => void` | - | Handler called when dragging ends | | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | The orientation of the slider | | `isDisabled` | `boolean` | - | Whether the slider is disabled | | `name` | `string` | - | The name of the input element for form submission | | `aria-label` | `string` | - | Accessibility label for the slider | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Slider content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### ColorSlider.Output | Prop | Type | Default | Description | | ----------- | ----------------------------- | ------- | --------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Output content or render function | ### ColorSlider.Track | Prop | Type | Default | Description | | ----------- | --------------------------------- | ------- | -------------------------------- | | `className` | `string` | - | Additional CSS classes | | `style` | `CSSProperties \| RenderFunction` | - | Inline styles or render function | | `children` | `ReactNode \| RenderFunction` | - | Track content or render function | ### ColorSlider.Thumb | Prop | Type | Default | Description | | ----------- | --------------------------------- | ------- | -------------------------------- | | `className` | `string` | - | Additional CSS classes | | `style` | `CSSProperties \| RenderFunction` | - | Inline styles or render function | | `children` | `ReactNode \| RenderFunction` | - | Thumb content or render function | ### Render Props When using render functions, these values are provided: | Prop | Type | Description | | ------------- | ---------------------------- | ------------------------------ | | `state` | `ColorSliderState` | The state of the color slider | | `color` | `Color` | The current color value | | `orientation` | `"horizontal" \| "vertical"` | The orientation of the slider | | `isDisabled` | `boolean` | Whether the slider is disabled | ## Accessibility The ColorSlider component implements the ARIA slider pattern and provides: * Full keyboard navigation support (Arrow keys, Home, End, Page Up/Down) * Screen reader announcements for value changes * Proper focus management * Support for disabled states * HTML form integration via hidden input elements * Internationalization support with locale-aware value formatting For more information, see the [React Aria ColorSlider documentation](https://react-spectrum.adobe.com/react-aria/ColorSlider.html). ## Related Components ## 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/en/docs/react/components/color-swatch-picker **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(colors)/color-swatch-picker.mdx > A list of color swatches that allows users to select a color from a predefined palette. ## Usage ```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) => ( ))} ); } ``` ## Anatomy ```tsx import { ColorSwatchPicker } from '@heroui/react'; export default () => ( ); ``` ## Examples ### Variants ```tsx import {ColorSwatchPicker} from "@heroui/react"; const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"]; export function Variants() { return (
Circle (default) {colors.map((color) => ( ))}
Square {colors.map((color) => ( ))}
); } ``` ### Sizes ```tsx import {ColorSwatchPicker} from "@heroui/react"; const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"]; const sizes = ["xs", "sm", "md", "lg", "xl"] as const; export function Sizes() { return (
{sizes.map((size) => (
{size} {colors.map((color) => ( ))}
))}
); } ``` ### Disabled ```tsx import {ColorSwatchPicker} from "@heroui/react"; const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"]; export function Disabled() { return ( {colors.map((color) => ( ))} ); } ``` ### Stack Layout ```tsx import {ColorSwatchPicker} from "@heroui/react"; const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"]; export function StackLayout() { return ( {colors.map((color) => ( ))} ); } ``` ### Default Value ```tsx import {ColorSwatchPicker} from "@heroui/react"; const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"]; export function DefaultValue() { return ( {colors.map((color) => ( ))} ); } ``` ### Controlled ```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) => ( ))}

Selected: {value.toString("hex")}

); } ``` ### Custom Indicator ```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) => ( ))} ); } ``` ### Render Function ```tsx "use client"; import {ColorSwatchPicker} from "@heroui/react"; const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"]; export function RenderFunction() { return (
}> {colors.map((color) => ( ))} ); } ``` ## Customization ### Tailwind CSS ```tsx import {ColorSwatchPicker} from "@heroui/react"; const colors = ["#F43F5E", "#D946EF", "#8B5CF6", "#3B82F6", "#06B6D4", "#10B981", "#84CC16"]; export function CustomStyles() { return ( {colors.map((color) => ( ))} ); } ``` ### Global CSS To customize the ColorSwatchPicker component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 ColorSwatchPicker component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-swatch-picker.css)): #### Base & Structure \[!toc] * `.color-swatch-picker` - Base container (flex layout) * `.color-swatch-picker__item` - Individual swatch item wrapper * `.color-swatch-picker__swatch` - The color swatch visual element #### Size Classes \[!toc] * `.color-swatch-picker--xs` - Extra small (16px) * `.color-swatch-picker--sm` - Small (24px) * `.color-swatch-picker--md` - Medium (32px, default) * `.color-swatch-picker--lg` - Large (36px) * `.color-swatch-picker--xl` - Extra large (40px) #### Shape Variants \[!toc] * `.color-swatch-picker--circle` - Circle shape (default) * `.color-swatch-picker--square` - Square shape with rounded corners #### Layout Classes \[!toc] * `.color-swatch-picker--grid` - Horizontal wrapping layout (default) * `.color-swatch-picker--stack` - Vertical stacked layout ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` - Scale up to 1.1 (only when not selected) * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` - Focus ring * **Selected**: `[data-selected="true"]` - Inner border with same color as swatch * **Disabled**: `[data-disabled="true"]` - Reduced opacity ## API Reference ### ColorSwatchPicker Inherits from [React Aria ColorSwatchPicker](https://react-spectrum.adobe.com/react-aria/ColorSwatchPicker.html). | Prop | Type | Default | Description | | -------------- | ------------------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------- | | `value` | `string \| Color` | - | The current selected color (controlled) | | `defaultValue` | `string \| Color` | - | The default selected color (uncontrolled) | | `onChange` | `(value: Color) => void` | - | Handler called when selection changes | | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size of the swatches | | `variant` | `"circle" \| "square"` | `"circle"` | Shape of the swatches | | `layout` | `"grid" \| "stack"` | `"grid"` | Layout direction | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | ColorSwatchPicker.Item elements | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### ColorSwatchPicker.Item | Prop | Type | Default | Description | | ------------ | ---------------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------- | | `color` | `string \| Color` | **Required** | The color of the swatch | | `isDisabled` | `boolean` | `false` | Whether the item is disabled | | `className` | `string` | - | Additional CSS classes | | `children` | `React.ReactNode` | - | ColorSwatchPicker.Swatch element | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### ColorSwatchPicker.Swatch | Prop | Type | Default | Description | | ----------- | -------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | ### parseColor The `parseColor` function is re-exported from React Aria Components for convenience: ```tsx import { parseColor } from '@heroui/react'; // Parse hex color const red = parseColor('#ff0000'); // Parse RGB const green = parseColor('rgb(0, 255, 0)'); // Parse HSL const blue = parseColor('hsl(240, 100%, 50%)'); ``` ## Related Components ## 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/en/docs/react/components/color-swatch **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(colors)/color-swatch.mdx > A visual preview of a color value with accessibility support ## Usage ```tsx import { ColorSwatch } from '@heroui/react'; ``` ```tsx import {ColorSwatch} from "@heroui/react"; export function ColorSwatchBasic() { return (
); } ``` ## Examples ### Sizes ```tsx import {ColorSwatch} from "@heroui/react"; export function ColorSwatchSizes() { return (
); } ``` ### Shapes ```tsx import {ColorSwatch} from "@heroui/react"; export function ColorSwatchShapes() { return (
); } ``` ### Transparency ```tsx import {ColorSwatch} from "@heroui/react"; export function ColorSwatchTransparency() { return (
); } ``` ### Render Function You can use the `style` render props to access the color value and create custom visual effects. ```tsx "use client"; import {ColorSwatch} from "@heroui/react"; export function RenderFunction() { return (
} />
} />
} />
} />
} />
); } ``` ### Accessibility Use `colorName` to provide a custom accessible name for the color, and `aria-label` to add context about how the color is used. ```tsx import {ColorSwatch} from "@heroui/react"; export function ColorSwatchAccessibility() { return (
); } ``` ## Customization ### Tailwind CSS ```tsx "use client"; import {ColorSwatch} from "@heroui/react"; export function ColorSwatchCustomStyles() { const colors = ["#0485F7", "#EF4444", "#F59E0B", "#10B981", "#D946EF"]; return (
{/* Glow effect */}
Glow Effect
{colors.map((color) => ( ({ boxShadow: `0 0 20px 2px ${color}`, })} /> ))}
{/* Gradient swatch */}
Gradient
{colors.map((color) => ( ({ background: `linear-gradient(135deg, ${c.toString("css")}, white)`, })} /> ))}
); } ``` ### Global CSS To customize the ColorSwatch component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .color-swatch { @apply border-2 border-white; } } ``` ## 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 ColorSwatch component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/color-swatch.css)): #### Base Classes \[!toc] * `.color-swatch` - Base swatch styles with checkered background for transparency #### Shape Classes \[!toc] * `.color-swatch--circle` - Circular shape (default) * `.color-swatch--square` - Square shape with rounded corners #### Size Classes \[!toc] * `.color-swatch--xs` - Extra small (16px) * `.color-swatch--sm` - Small (24px) * `.color-swatch--md` - Medium (32px, default) * `.color-swatch--lg` - Large (36px) * `.color-swatch--xl` - Extra large (40px) ## API Reference ### ColorSwatch | Prop | Type | Default | Description | | ------------ | ------------------------------------------------------------------------------ | ---------- | -------------------------------------------------------------------- | | `color` | `string \| Color` | - | The color value to display (hex, rgb, hsl, etc.) | | `colorName` | `string` | - | Accessible name for the color (overrides auto-generated description) | | `className` | `string` | - | Additional CSS classes | | `shape` | `"circle" \| "square"` | `"circle"` | Shape of the swatch | | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size of the swatch | | `style` | `CSSProperties \| ((renderProps) => CSSProperties)` | - | Inline styles or render props function with access to color | | `aria-label` | `string` | - | Accessible label for the swatch | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### Style Render Props When using the `style` prop as a function, you receive render props with access to the color: ```tsx ({ boxShadow: `0 4px 14px ${color.toString("css")}80`, })} /> ``` The `color` object provides methods like: * `color.toString("css")` - Returns CSS color string * `color.toString("hex")` - Returns hex color string * `color.getChannelValue("alpha")` - Returns alpha channel value ## Related Components ## 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/en/docs/react/components/slider **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(controls)/slider.mdx > A slider allows a user to select one or more values within a range ## Usage ```tsx import { Slider } from '@heroui/react'; ``` ```tsx import {Label, Slider} from "@heroui/react"; export function Default() { return ( ); } ``` ## Anatomy ```tsx import { Slider, Label } from '@heroui/react'; export default () => ( ) ``` `Slider.Steps` and `Slider.Marks` are optional. Compose them independently as needed. ## Examples ### Steps Place `Slider.Steps` inside `Slider.Track` to display a dot at each increment defined by `minValue`, `maxValue`, and `step`. The `step` prop controls value increments even without visible dots. Dots start at `minValue`. If `maxValue` is not on a step, no extra dot is added at that endpoint. Invalid ranges and ranges requiring more than 1,000 dots omit the indicators. ```tsx import {Label, Slider} from "@heroui/react"; export function WithSteps() { return ( ); } ``` ### Marks Place `Slider.Marks` directly after `Slider.Track` as a sibling, and add `Slider.Mark` children with a required `value`. Each label is positioned at its value, so marks can be unevenly spaced and do not need to match the step increments. Use children for custom labels. Without children, a mark displays its value using the slider's locale and `formatOptions`. Marks outside the slider's bounds, or with non-finite values, are not rendered. Marks are labels; they do not change the value or snapping behavior. ```tsx import {Label, Slider} from "@heroui/react"; export function WithMarks() { return ( Off Quiet Loud Max ); } ``` ### Steps and Marks Combine dots and labels independently. Fractional steps are supported; this example uses `step={0.1}` with labels at `0`, `0.5`, and `1`. ```tsx import {Label, Slider} from "@heroui/react"; export function WithStepsAndMarks() { return ( ); } ``` ### Range with Steps and Marks For a single thumb, dots and marks from the minimum through the selected value receive `data-filled="true"`. For a range, only those between the first and last thumbs receive it, including the endpoints. ```tsx "use client"; import {Label, Slider} from "@heroui/react"; export function RangeWithStepsAndMarks() { return ( {({state}) => ( <> {state.values.map((_, index) => ( ))} )} ); } ``` ### Vertical Steps and Marks Dots and labels follow `orientation="vertical"` automatically. Give vertical sliders a container with a defined height. Horizontal indicators also follow the locale's right-to-left direction. ```tsx import {Label, Slider} from "@heroui/react"; export function VerticalWithStepsAndMarks() { return (
); } ``` ### Disabled ```tsx import {Label, Slider} from "@heroui/react"; export function Disabled() { return ( ); } ``` ### Range Slider Anatomy ```tsx import { Slider, Label } from '@heroui/react'; export default () => ( ) ``` ### Vertical ```tsx import {Label, Slider} from "@heroui/react"; export function Vertical() { return (
); } ``` ### Range ```tsx "use client"; import {Label, Slider} from "@heroui/react"; export function Range() { return ( {({state}) => ( <> {state.values.map((_, i) => ( ))} )} ); } ``` ### Controlled Value Control the value with `value` and `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); } }} >

Current value: {value}

); } ``` ### Custom Value Formatting Use `formatOptions` to format the displayed value. This example formats it as USD. ```tsx import {Label, Slider} from "@heroui/react"; export function CustomValueFormatting() { return ( ); } ``` ### Custom Output Customize `Slider.Output` to control how the selected values are shown. ```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) => ( ))} )} ); } ``` ### Render Function ```tsx "use client"; import {Label, Slider} from "@heroui/react"; export function RenderFunction() { return (
} > ); } ``` ## Customization ### Tailwind CSS ```tsx import {Label, Slider} from "@heroui/react"; export function CustomStyles() { return ( ); } ``` ### Global CSS To customize the Slider component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 Slider component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/slider.css)): #### Base Classes \[!toc] * `.slider` - Base slider container * `.slider__output` - Output element displaying current value(s) * `.slider__track` - Track element containing fill, optional step dots, and thumbs * `.slider__fill` - Fill element showing selected range * `.slider__thumb` - Individual thumb element * `.slider__steps` - Decorative step indicator container inside the track * `.slider__step` - Individual step dot * `.slider__marks` - Mark label container after the track * `.slider__mark` - Individual value-positioned label #### State Classes \[!toc] * `.slider[data-disabled="true"]` - Disabled slider state * `.slider[data-orientation="vertical"]` - Vertical orientation * `.slider__thumb[data-dragging="true"]` - Thumb being dragged * `.slider__thumb[data-focus-visible="true"]` - Thumb keyboard focused * `.slider__thumb[data-disabled="true"]` - Disabled thumb state * `.slider__track[data-fill-start="true"]` - Fill starts at beginning * `.slider__track[data-fill-end="true"]` - Fill ends at end * `.slider__step[data-filled="true"]` - Step dot within the selected interval * `.slider__mark[data-filled="true"]` - Mark label within the selected interval ### Indicator Data Attributes | Attribute | Applies to | Description | | ---------------------- | ------------------------------------ | ------------------------------------------------------------------------------ | | `data-slot` | Steps, step dots, marks, mark labels | `slider-steps`, `slider-step`, `slider-marks`, and `slider-mark`, respectively | | `data-value` | Step dots and mark labels | The numeric value represented by the indicator | | `data-filled="true"` | Step dots and mark labels | The value is within the selected interval, including endpoints | | `data-disabled="true"` | Steps, step dots, marks, mark labels | Inherits the slider's disabled state | | `data-orientation` | Steps and marks containers | `horizontal` or `vertical` | | `data-direction` | Steps and marks containers | `ltr` or `rtl`, derived from the locale | ### Interactive States The component supports both CSS pseudo-classes and data attributes for flexibility: * **Hover**: `:hover` or `[data-hovered="true"]` on thumb * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` on thumb * **Dragging**: `[data-dragging="true"]` on thumb * **Disabled**: `:disabled` or `[data-disabled="true"]` on slider or thumb ## API Reference ### Slider | Prop | Type | Default | Description | | ----------------- | ------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------------------- | | `value` | `number \| number[]` | - | The current value (controlled) | | `defaultValue` | `number \| number[]` | - | The default value (uncontrolled) | | `onChange` | `(value: number \| number[]) => void` | - | Handler called when the value changes | | `onChangeEnd` | `(value: number \| number[]) => void` | - | Handler called when dragging ends | | `minValue` | `number` | `0` | The slider's minimum value | | `maxValue` | `number` | `100` | The slider's maximum value | | `step` | `number` | `1` | Value increment and spacing between dots when `Slider.Steps` is present | | `formatOptions` | `Intl.NumberFormatOptions` | - | The display format of the value label | | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | The orientation of the slider | | `isDisabled` | `boolean` | - | Whether the slider is disabled | | `aria-label` | `string` | - | Accessibility label for the slider | | `aria-labelledby` | `string` | - | ID of element that labels the slider | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Slider content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### Slider.Output | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Output content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### Slider.Track | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------- | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Track content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### Slider.Fill | Prop | Type | Default | Description | | ----------- | --------------- | ------- | ---------------------- | | `className` | `string` | - | Additional CSS classes | | `style` | `CSSProperties` | - | Inline styles | ### Slider.Thumb | Prop | Type | Default | Description | | ------------ | ------------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------- | | `index` | `number` | `0` | Index of the thumb within the slider | | `isDisabled` | `boolean` | - | Whether this thumb is disabled | | `name` | `string` | - | The name of the input element, used when submitting an HTML form | | `className` | `string` | - | Additional CSS classes | | `children` | `ReactNode \| RenderFunction` | - | Thumb content or render function | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function. | ### Slider.Steps Renders decorative dots inside `Slider.Track` using the root slider's bounds and step. It generates its own children and accepts standard DOM props and a ref. | Prop | Type | Default | Description | | ----------- | ----------------------------------------------------------------- | ------- | --------------------------------------------------------------- | | `className` | `string` | - | Additional CSS classes for the container | | `style` | `CSSProperties` | - | Inline styles for the container | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function | ### Slider.Marks Contains `Slider.Mark` children. Place it directly inside `Slider`, after `Slider.Track`. Accepts standard DOM props and a ref. | Prop | Type | Default | Description | | ----------- | ----------------------------------------------------------------- | ------- | --------------------------------------------------------------- | | `children` | `ReactNode` | - | Mark labels | | `className` | `string` | - | Additional CSS classes for the container | | `style` | `CSSProperties` | - | Inline styles for the container | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function | ### Slider.Mark A label positioned at a value within `Slider.Marks`. Accepts standard DOM props and a ref. | Prop | Type | Default | Description | | ----------- | ----------------------------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------- | | `value` | `number` | Required | Value at which to position the label; non-finite or out-of-bounds values are not rendered | | `children` | `ReactNode` | Formatted value | Custom label content; otherwise uses the slider's locale and `formatOptions` | | `className` | `string` | - | Additional CSS classes | | `style` | `CSSProperties` | - | Additional inline styles; value positioning is managed automatically | | `render` | `DOMRenderFunction` | - | Overrides the default DOM element with a custom render function | ### Render Props When using render functions with Slider.Output or Slider.Track, these values are provided: | Prop | Type | Description | | -------------------- | ---------------------------- | -------------------------------------------------------- | | `state` | `SliderState` | The state of the slider | | `values` | `number[]` | Values managed by the slider by thumb index | | `getThumbValueLabel` | `(index: number) => string` | Returns the string label for the specified thumb's value | | `orientation` | `"horizontal" \| "vertical"` | The orientation of the slider | | `isDisabled` | `boolean` | Whether the slider is disabled | ## Accessibility The Slider component implements the ARIA slider pattern and provides: * Full keyboard navigation support (Arrow keys, Home, End, Page Up/Down) * Screen reader announcements for value changes * Proper focus management * Support for disabled states * HTML form integration via hidden input elements * Internationalization support with locale-aware value formatting * Right-to-left (RTL) language support * Step dots are decorative, hidden from assistive technology, and add no tab stops * Mark labels remain readable text and do not replace the slider's accessible label or value announcements For more information, see the [React Aria Slider documentation](https://react-spectrum.adobe.com/react-aria/Slider.html). ## Related Components ## 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/en/docs/react/components/switch **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(controls)/switch.mdx > A toggle switch component for boolean states ## Usage ```tsx import { Switch, SwitchGroup, Label } from '@heroui/react'; ``` ```tsx import {Switch} from "@heroui/react"; export function Basic() { return ( Enable notifications ); } ``` ## Anatomy ```tsx import { Switch, Label, Description } from '@heroui/react'; export default () => ( {/* Optional */} ); ``` For grouping multiple switches, use the `SwitchGroup` component: ```tsx import { Switch, SwitchGroup, Label } from '@heroui/react'; export default () => ( ); ``` ## Examples ### Sizes ```tsx import {Switch} from "@heroui/react"; export function Sizes() { return (
Small Medium Large
); } ``` ### With Icons ```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 ? ( ) : ( )} )} ))}
); } ``` ### Disabled ```tsx import {Switch} from "@heroui/react"; export function Disabled() { return ( Enable notifications ); } ``` ### Without Label ```tsx import {Switch} from "@heroui/react"; export function WithoutLabel() { return ( ); } ``` ### With Description ```tsx import {Description, Switch} from "@heroui/react"; export function WithDescription() { return (
Public profile Allow others to see your profile information
); } ``` ### Default Selected ```tsx import {Switch} from "@heroui/react"; export function DefaultSelected() { return ( Enable notifications ); } ``` ### Controlled ```tsx "use client"; import {Switch} from "@heroui/react"; import React from "react"; export function Controlled() { const [isSelected, setIsSelected] = React.useState(false); return (
Enable notifications

Switch is {isSelected ? "on" : "off"}

); } ``` ### Label Position ```tsx import {Switch} from "@heroui/react"; export function LabelPosition() { return (
Label after Label before
); } ``` ### Group ```tsx import {Switch, SwitchGroup} from "@heroui/react"; export function Group() { return ( Allow Notifications Marketing emails Social media updates ); } ``` ### Group Horizontal ```tsx import {Switch, SwitchGroup} from "@heroui/react"; export function GroupHorizontal() { return ( Notifications Marketing Social ); } ``` ### Form Integration ```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( `Form submitted with:\n${Array.from(formData.entries()) .map(([key, value]) => `${key}: ${value}`) .join("\n")}`, ); }; return (
Enable notifications Subscribe to newsletter Receive marketing updates
); } ``` ### Render Props ```tsx "use client"; import {Switch} from "@heroui/react"; export function RenderProps() { return ( {({isSelected}) => ( {isSelected ? "Enabled" : "Disabled"} )} ); } ``` ### Render Function ```tsx "use client"; import {Switch} from "@heroui/react"; export function RenderFunction() { return (
}> Enable notifications ); } ``` ## Customization ### Tailwind CSS ```tsx "use client"; import {Description, Label, Switch} from "@heroui/react"; export function CustomStyles() { return (
Changes are saved as you type.
); } ``` ### Global CSS To customize the Switch component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## Styling Reference HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize. ### CSS Classes #### Switch Classes \[!toc] The Switch component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/switch.css)): * `.switch` - Base switch container * `.switch__content` - Optional content container * `.switch__control` - Switch control track * `.switch__thumb` - Switch thumb that moves * `.switch__icon` - Optional icon inside the thumb * `.switch--sm` - Small size variant * `.switch--md` - Medium size variant (default) * `.switch--lg` - Large size variant #### SwitchGroup Classes \[!toc] The SwitchGroup component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/switch-group.css)): * `.switch-group` - Switch group container * `.switch-group__items` - Container for switch items * `.switch-group--horizontal` - Horizontal layout * `.switch-group--vertical` - Vertical layout (default) ### Interactive States The switch supports both CSS pseudo-classes and data attributes for flexibility: * **Selected**: `[data-selected="true"]` (thumb position and background color change) * **Hover**: `:hover` or `[data-hovered="true"]` * **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring) * **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events) * **Pressed**: `:active` or `[data-pressed="true"]` ## API Reference ### Switch Inherits from [React Aria Switch](https://react-spectrum.adobe.com/react-aria/Switch.html). | Prop | Type | Default | Description | | -------------------- | ------------------------------------------------------------------------- | ---------- | ----------------------------------------------------------------- | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | The size of the switch | | `isSelected` | `boolean` | `false` | Whether the switch is on | | `defaultSelected` | `boolean` | `false` | Whether the switch is on by default (uncontrolled) | | `isDisabled` | `boolean` | `false` | Whether the switch is disabled | | `isInvalid` | `boolean` | `false` | Whether the switch is invalid | | `isReadOnly` | `boolean` | `false` | Whether the switch is read only | | `isRequired` | `boolean` | `false` | Whether the switch 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 | | `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 switch value changes | | `onPress` | `(e: PressEvent) => void` | - | Handler called when the switch is pressed | | `children` | `React.ReactNode \| (values: SwitchRenderProps) => React.ReactNode` | - | Switch 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 | | ---------------- | --------- | --------------------------------------- | | `isSelected` | `boolean` | Whether the switch is currently on | | `isHovered` | `boolean` | Whether the switch is hovered | | `isPressed` | `boolean` | Whether the switch is currently pressed | | `isFocused` | `boolean` | Whether the switch is focused | | `isFocusVisible` | `boolean` | Whether the switch is keyboard focused | | `isDisabled` | `boolean` | Whether the switch is disabled | | `isReadOnly` | `boolean` | Whether the switch is read only | | `isInvalid` | `boolean` | Whether the switch is invalid | | `isRequired` | `boolean` | Whether the switch is required | | `state` | `-` | State of the switch. | ### SwitchGroup | Prop | Type | Default | Description | | ------------- | ---------------------------- | ------------ | ----------------------------------- | | `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | The orientation of the switch group | | `children` | `React.ReactNode` | - | The switch items to render | | `className` | `string` | - | Additional CSS class names | ## Related Components ## 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/en/docs/react/components/badge **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(data-display)/badge.mdx > Displays a small indicator positioned relative to another element, commonly used for notification counts, status dots, and labels ## Usage ```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 (
JD 5 AB New CD
); } ``` ## Anatomy Badge is designed to be positioned relative to another element using `Badge.Anchor`. Plain-text children are automatically wrapped in ``. > For standalone label usage, use the [Chip](/docs/react/components/chip) component instead. ```tsx 5 ``` ## Examples ### Variants ```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"; export function BadgeVariants() { const variants = ["primary", "secondary", "soft"] as const; const colors = ["accent", "default", "success", "warning", "danger"] as const; return (
{variants.map((variant, index) => (

{variant}

{colors.map((color) => ( JD 5 ))}
{index < variants.length - 1 && }
))}
); } ``` ### Sizes ```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 ))}
); } ``` ### Colors ```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 ))}
); } ``` ### Placements ```tsx import {Avatar, Badge} from "@heroui/react"; const AVATAR_URL = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg"; export function BadgePlacements() { const placements = ["top-right", "top-left", "bottom-right", "bottom-left"] as const; return (
{placements.map((placement) => (
JD {placement}
))}
); } ``` ### Dot Badge Empty badges act as status indicators — useful for online/offline states or activity signals. ```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 ))}
); } ``` ### With Content Badge supports text, numbers, and icons as content. When no children are provided, it renders as a dot indicator. ```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 (
JD 5 JD New JD 99+ JD
); } ``` ## Customization ### Tailwind CSS ```tsx import {Avatar, Badge} from "@heroui/react"; export function CustomStyles() { return ( KW 5 ); } ``` ### Global CSS To customize the Badge component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 Badge component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/badge.css)): #### Base Classes \[!toc] * `.badge` - Base badge container styles * `.badge__label` - Label text slot styles * `.badge-anchor` - Positioning wrapper for the anchored element #### Color Classes \[!toc] * `.badge--accent` - Accent color variant * `.badge--danger` - Danger color variant * `.badge--default` - Default color variant * `.badge--success` - Success color variant * `.badge--warning` - Warning color variant #### Variant Classes \[!toc] * `.badge--primary` - Primary variant with filled background * `.badge--secondary` - Secondary variant with default background * `.badge--soft` - Soft variant with lighter background #### Size Classes \[!toc] * `.badge--sm` - Small size * `.badge--md` - Medium size (default) * `.badge--lg` - Large size #### Placement Classes \[!toc] * `.badge--top-right` - Position at top-right corner (default) * `.badge--top-left` - Position at top-left corner * `.badge--bottom-right` - Position at bottom-right corner * `.badge--bottom-left` - Position at bottom-left corner #### Compound Variant Classes \[!toc] Badges support combining variant and color classes (e.g., `.badge--primary.badge--accent`). The following combinations have default styles defined: **Primary Variants:** * `.badge--primary.badge--accent` - Primary accent with filled background * `.badge--primary.badge--default` - Primary default with filled background * `.badge--primary.badge--success` - Primary success with filled background * `.badge--primary.badge--warning` - Primary warning with filled background * `.badge--primary.badge--danger` - Primary danger with filled background **Soft Variants:** * `.badge--soft.badge--accent` - Soft accent with lighter background * `.badge--soft.badge--default` - Soft default with lighter background * `.badge--soft.badge--success` - Soft success with lighter background * `.badge--soft.badge--warning` - Soft warning with lighter background * `.badge--soft.badge--danger` - Soft danger with lighter background ## API Reference ### Badge | Prop | Type | Default | Description | | ----------- | -------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------- | | `children` | `React.ReactNode` | - | Content to display inside the badge (text, number, or icon). When omitted, renders as a dot. | | `className` | `string` | - | Additional CSS classes for the root element | | `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Color variant of the badge | | `variant` | `"primary" \| "secondary" \| "soft"` | `"primary"` | Visual style variant | | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the badge | | `placement` | `"top-right" \| "top-left" \| "bottom-right" \| "bottom-left"` | `"top-right"` | Position of the badge relative to its anchor | ### Badge.Anchor | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | --------------------------------------------------------- | | `children` | `React.ReactNode` | - | The element to anchor the badge to, plus the Badge itself | | `className` | `string` | - | Additional CSS classes for the anchor wrapper | ### Badge.Label | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ----------------------------------------- | | `children` | `React.ReactNode` | - | Label text content | | `className` | `string` | - | Additional CSS classes for the label slot | ## Related Components ## Related Components * **Avatar**: Display user profile images * **Chip**: Compact elements for tags and filters
# Chip **Category**: react **URL**: https://heroui.com/en/docs/react/components/chip **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(data-display)/chip.mdx > Small informational badges for displaying labels, statuses, and categories ## Usage ```tsx import { Chip } from '@heroui/react'; ``` ```tsx import {Chip} from "@heroui/react"; export function ChipBasic() { return (
Default Accent Success Warning Danger
); } ``` ## Anatomy > Plain-text children are automatically wrapped in ``. ```tsx Label text ``` ## Examples ### Variants ```tsx import {CircleDashed} from "@gravity-ui/icons"; import {Chip, Separator} from "@heroui/react"; import React from "react"; export function ChipVariants() { const sizes = ["lg", "md", "sm"] as const; const variants = ["primary", "secondary", "tertiary", "soft"] as const; const colors = ["accent", "default", "success", "warning", "danger"] as const; return (
{sizes.map((size, index) => (

{size}

{/* Color labels header */}
{colors.map((color) => (
{color}
))}
{variants.map((variant) => (
{variant}
{colors.map((color) => (
Label
))}
))}
{index < sizes.length - 1 && } ))}
); } ``` ### Statuses ```tsx import {Ban, Check, CircleFill, CircleInfo, TriangleExclamation} from "@gravity-ui/icons"; import {Chip} from "@heroui/react"; export function ChipStatuses() { return (
Default Active Pending Inactive
New Feature Available Beta Deprecated
); } ``` ### With Icons ```tsx import {ChevronDown, CircleCheckFill, CircleFill, Clock, Xmark} from "@gravity-ui/icons"; import {Chip} from "@heroui/react"; export function ChipWithIcon() { return (
Information Completed Pending Failed Label
); } ``` ## Customization ### Tailwind CSS ```tsx import {Chip} from "@heroui/react"; export function CustomStyles() { return (
Draft In review Published
); } ``` ### Global CSS To customize the Chip component classes, you can use the `@layer components` directive. [Learn more](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; } } ``` ## 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 Chip component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/chip.css)): #### Base Classes \[!toc] * `.chip` - Base chip container styles * `.chip__label` - Label text slot styles #### Color Classes \[!toc] * `.chip--accent` - Accent color variant * `.chip--danger` - Danger color variant * `.chip--default` - Default color variant * `.chip--success` - Success color variant * `.chip--warning` - Warning color variant #### Variant Classes \[!toc] * `.chip--primary` - Primary variant with filled background * `.chip--secondary` - Secondary variant with border * `.chip--tertiary` - Tertiary variant with transparent background * `.chip--soft` - Soft variant with lighter background #### Size Classes \[!toc] * `.chip--sm` - Small size * `.chip--md` - Medium size (default) * `.chip--lg` - Large size #### Compound Variant Classes \[!toc] Chips support combining variant and color classes (e.g., `.chip--secondary.chip--accent`). The following combinations have default styles defined: **Primary Variants:** * `.chip--primary.chip--accent` - Primary accent combination with filled background * `.chip--primary.chip--success` - Primary success combination with filled background * `.chip--primary.chip--warning` - Primary warning combination with filled background * `.chip--primary.chip--danger` - Primary danger combination with filled background **Soft Variants:** * `.chip--accent.chip--soft` - Soft accent combination with lighter background * `.chip--success.chip--soft` - Soft success combination with lighter background * `.chip--warning.chip--soft` - Soft warning combination with lighter background * `.chip--danger.chip--soft` - Soft danger combination with lighter background **Note:** You can apply custom styles to any variant-color combination (e.g., `.chip--secondary.chip--accent`, `.chip--tertiary.chip--success`) using the `@layer components` directive in your CSS. ## API Reference ### Chip | Prop | Type | Default | Description | | ----------- | ------------------------------------------------------------- | ------------- | ------------------------------------------- | | `children` | `React.ReactNode` | - | Content to display inside the chip | | `className` | `string` | - | Additional CSS classes for the root element | | `color` | `"default" \| "accent" \| "success" \| "warning" \| "danger"` | `"default"` | Color variant of the chip | | `variant` | `"primary" \| "secondary" \| "tertiary" \| "soft"` | `"secondary"` | Visual style variant | | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the chip | ### Chip.Label | Prop | Type | Default | Description | | ----------- | ----------------- | ------- | ----------------------------------------- | | `children` | `React.ReactNode` | - | Label text content | | `className` | `string` | - | Additional CSS classes for the label slot | ## Related Components ## Related Components * **Avatar**: Display user profile images * **CloseButton**: Button for dismissing overlays * **Separator**: Visual divider between content # Table **Category**: react **URL**: https://heroui.com/en/docs/react/components/table **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/en/react/components/(data-display)/table.mdx > Tables display structured data in rows and columns with support for sorting, selection, column resizing, and infinite scrolling. ## Usage ```tsx import { Table } from '@heroui/react'; ``` ```tsx import {Table} from "@heroui/react"; export function Basic() { return ( Name Role Status Email Kate Moore CEO Active kate@acme.com John Smith CTO Active john@acme.com Sara Johnson CMO On Leave sara@acme.com Michael Brown CFO Active michael@acme.com
); } ``` ## Anatomy ```tsx import { Table } from '@heroui/react'; export default () => ( Name Role Kate Moore CEO {/* Optional footer content */}
); ``` ## Examples ### Secondary Variant ```tsx import {Table} from "@heroui/react"; export function SecondaryVariant() { return ( Name Role Status Email Kate Moore CEO Active kate@acme.com John Smith CTO Active john@acme.com Sara Johnson CMO On Leave sara@acme.com Michael Brown CFO Active michael@acme.com
); } ``` ### Async Loading Use `Table.LoadMore` for infinite scrolling. It renders a sentinel row that triggers `onLoadMore` when scrolled into view. ```tsx "use client"; import {Chip, Spinner, Table} from "@heroui/react"; import {useCallback, useRef, useState} from "react"; interface User { id: number; name: string; role: string; status: string; email: string; } const statusColorMap: Record = { Active: "success", Inactive: "danger", "On Leave": "warning", }; const allUsers: User[] = [ {email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"}, {email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"}, {email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"}, {email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"}, { email: "emily@acme.com", id: 5, name: "Emily Davis", role: "Product Manager", status: "Inactive", }, {email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "Lead Designer", status: "Active"}, { email: "olivia@acme.com", id: 7, name: "Olivia Martinez", role: "Frontend Engineer", status: "Active", }, { email: "james@acme.com", id: 8, name: "James Taylor", role: "Backend Engineer", status: "Active", }, { email: "sophia@acme.com", id: 9, name: "Sophia Anderson", role: "QA Engineer", status: "On Leave", }, {email: "liam@acme.com", id: 10, name: "Liam Thomas", role: "DevOps Engineer", status: "Active"}, { email: "lucas@acme.com", id: 11, name: "Lucas Martinez", role: "Product Manager", status: "Active", }, { email: "emma@acme.com", id: 12, name: "Emma Johnson", role: "Frontend Engineer", status: "Active", }, {email: "noah@acme.com", id: 13, name: "Noah Davis", role: "Backend Engineer", status: "Active"}, {email: "ava@acme.com", id: 14, name: "Ava Wilson", role: "Lead Designer", status: "Active"}, { email: "oliver@acme.com", id: 15, name: "Oliver Martinez", role: "Frontend Engineer", status: "Active", }, { email: "isabella@acme.com", id: 16, name: "Isabella Johnson", role: "Backend Engineer", status: "Active", }, {email: "mia@acme.com", id: 17, name: "Mia Davis", role: "Lead Designer", status: "Active"}, { email: "william@acme.com", id: 18, name: "William Wilson", role: "Frontend Engineer", status: "Active", }, ]; const ITEMS_PER_PAGE = 6; const columns = [ {id: "name", name: "Name"}, {id: "role", name: "Role"}, {id: "status", name: "Status"}, {id: "email", name: "Email"}, ]; export function AsyncLoading() { const [items, setItems] = useState(() => allUsers.slice(0, ITEMS_PER_PAGE)); const [isLoading, setIsLoading] = useState(false); const isLoadingRef = useRef(false); const hasMore = items.length < allUsers.length; const loadMore = useCallback(() => { if (!hasMore || isLoadingRef.current) return; isLoadingRef.current = true; setIsLoading(true); setTimeout(() => { setItems((prev) => allUsers.slice(0, prev.length + ITEMS_PER_PAGE)); setIsLoading(false); requestAnimationFrame(() => { isLoadingRef.current = false; }); }, 1500); }, [hasMore]); return ( {columns.map((col) => ( {col.name} ))} {(user) => ( {user.name} {user.role} {user.status} {user.email} )} {!!hasMore && ( )}
); } ``` ### Sorting Columns can be made sortable using the `allowsSorting` prop on `Table.Column`. Use `sortDescriptor` and `onSortChange` on `Table.Content` to manage sort state. Wrap the label in `Table.SortableColumnHeader` and forward the `sortDirection` from the column's render prop to render the default ascending/descending indicator. ```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: "CEO", status: "Active"}, {email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"}, {email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"}, {email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"}, { email: "emily@acme.com", id: 5, name: "Emily Davis", role: "Product Manager", status: "Inactive", }, ]; export function Sorting() { const [sortDescriptor, setSortDescriptor] = useState({ column: "name", direction: "ascending", }); const sortedUsers = useMemo(() => { return [...users].sort((a, b) => { const col = sortDescriptor.column as keyof User; const first = String(a[col]); const second = String(b[col]); let cmp = first.localeCompare(second); if (sortDescriptor.direction === "descending") { cmp *= -1; } return cmp; }); }, [sortDescriptor]); return ( {({sortDirection}) => ( Name )} {({sortDirection}) => ( Role )} {({sortDirection}) => ( Status )} {({sortDirection}) => ( Email )} {sortedUsers.map((user) => ( {user.name} {user.role} {user.status} {user.email} ))}
); } ``` ### Selection Enable row selection with `selectionMode` on `Table.Content`. Use `Checkbox` with `slot="selection"` for select-all and per-row checkboxes. ```tsx "use client"; import type {Selection} from "@heroui/react"; import {Checkbox, Table} from "@heroui/react"; import {useState} from "react"; const users = [ {email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"}, {email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"}, {email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"}, {email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"}, ]; export function SelectionDemo() { const [selectedKeys, setSelectedKeys] = useState(new Set()); return (
Name Role Status Email {users.map((user) => ( {user.name} {user.role} {user.status} {user.email} ))}

Selected:{" "} {selectedKeys === "all" ? "All" : selectedKeys.size > 0 ? Array.from(selectedKeys).join(", ") : "None"}

); } ``` ### Expandable Rows Rows can be nested to display hierarchical data. Use the `treeColumn` prop to designate a column, and render a `Button` with `slot="chevron"` in that column’s cells so users can expand and collapse the row. Use the `expandedKeys` prop to control which rows are expanded. ```tsx "use client"; import type {Selection} from "@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: "Weekly Report", type: "File"}, {children: [], date: "8/20/2025", id: "4", title: "Budget", type: "File"}, ], date: "8/2/2025", id: "2", title: "Project", type: "Directory", }, ], date: "10/20/2025", id: "1", title: "Documents", type: "Directory", }, { children: [ {children: [], date: "1/23/2026", id: "6", title: "Image 1", type: "File"}, {children: [], date: "2/3/2026", id: "7", title: "Image 2", type: "File"}, ], date: "2/3/2026", id: "5", title: "Photos", type: "Directory", }, ]; const [expandedKeys, setExpandedKeys] = useState(() => new Set(["1"])); const renderExpandableRow = (item: Row) => { return ( {({hasChildItems, isDisabled, isExpanded, isTreeColumn}) => ( {hasChildItems && isTreeColumn ? ( ) : null} {item.title} )} {item.type} {item.date} {renderExpandableRow} ); }; return ( Name Type Date Modified {renderExpandableRow}
); } ``` ### Pagination Use `Table.Footer` to add a pagination component below the table. ```tsx "use client"; import {Pagination, Table} from "@heroui/react"; import {useMemo, useState} from "react"; const columns = [ {id: "name", name: "Name"}, {id: "role", name: "Role"}, {id: "status", name: "Status"}, {id: "email", name: "Email"}, ]; const users = [ {email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"}, {email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"}, {email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"}, {email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"}, { email: "emily@acme.com", id: 5, name: "Emily Davis", role: "Product Manager", status: "Inactive", }, {email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "Lead Designer", status: "Active"}, { email: "olivia@acme.com", id: 7, name: "Olivia Martinez", role: "Frontend Engineer", status: "Active", }, { email: "james@acme.com", id: 8, name: "James Taylor", role: "Backend Engineer", status: "Active", }, ]; const ROWS_PER_PAGE = 4; export function PaginationDemo() { const [page, setPage] = useState(1); const totalPages = Math.ceil(users.length / ROWS_PER_PAGE); const pages = Array.from({length: totalPages}, (_, i) => i + 1); const paginatedItems = useMemo(() => { const start = (page - 1) * ROWS_PER_PAGE; return users.slice(start, start + ROWS_PER_PAGE); }, [page]); const start = (page - 1) * ROWS_PER_PAGE + 1; const end = Math.min(page * ROWS_PER_PAGE, users.length); return ( {(column) => ( {column.name} )} {(user) => ( {(column) => {user[column.id as keyof typeof user]}} )} {start} to {end} of {users.length} results setPage((p) => Math.max(1, p - 1))} > Prev {pages.map((p) => ( setPage(p)}> {p} ))} setPage((p) => Math.min(totalPages, p + 1))} > Next
); } ``` ### Column Resizing Wrap the table in `Table.ResizableContainer` and add `Table.ColumnResizer` inside each resizable column. ```tsx import {Chip, Table} from "@heroui/react"; export function ColumnResizing() { return ( Name Role Status Email Kate Moore CEO Active kate@acme.com John Smith CTO Active john@acme.com Sara Johnson CMO On Leave sara@acme.com Michael Brown CFO Active michael@acme.com Emily Davis Product Manager Inactive emily@acme.com
); } ``` ### Empty State Use `renderEmptyState` on `Table.Body` to display a custom message when the table has no data. ```tsx "use client"; import {EmptyState, Table} from "@heroui/react"; import {Icon} from "@iconify/react"; export function EmptyStateDemo() { return ( Name Role Status Email ( No results found )} > {[]}
); } ``` ### Virtualization Table supports virtualization through [Virtualizer](https://react-aria.adobe.com/Virtualizer), enabling efficient rendering of large datasets by displaying only the rows visible within the viewport. ```tsx "use client"; import {Table, TableLayout, Virtualizer} from "@heroui/react"; interface User { id: number; name: string; role: string; email: string; } export function Virtualization() { const roles = [ "Software Engineer", "Senior Engineer", "Staff Engineer", "Product Manager", "Designer", "Data Analyst", "QA Engineer", "DevOps Engineer", "Marketing Manager", "Sales Representative", ]; const firstNames = [ "Emma", "Liam", "Olivia", "Noah", "Ava", "James", "Sophia", "Oliver", "Isabella", "Lucas", "Mia", "Ethan", "Charlotte", "Mason", "Amelia", "Logan", "Harper", "Alexander", "Ella", "Benjamin", ]; const lastNames = [ "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Anderson", "Taylor", "Thomas", "Jackson", "White", "Harris", "Clark", "Lewis", "Robinson", "Walker", ]; function generateUsers(count: number): User[] { const users: User[] = []; for (let i = 0; i < count; i++) { const firstName = firstNames[i % firstNames.length]; const lastName = lastNames[Math.floor(i / firstNames.length) % lastNames.length]; const name = `${firstName} ${lastName}`; users.push({ email: `${firstName?.toLowerCase()}.${lastName?.toLowerCase()}@acme.com`, id: i + 1, name, role: roles[i % roles.length] || "", }); } return users; } const virtualizedUsers = generateUsers(1000); return ( Name Role Email {(user) => ( {user.name} {user.role} {user.email} )}
); } ``` ### TanStack Table HeroUI's Table works as a rendering layer on top of headless table libraries. This example uses [TanStack Table](https://tanstack.com/table) for column definitions, sorting, and pagination — while HeroUI handles styling and accessibility. ```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: "Active" | "Inactive" | "On Leave"; email: string; } const statusColorMap: Record = { Active: "success", Inactive: "danger", "On Leave": "warning", }; const users: User[] = [ {email: "kate@acme.com", id: 1, name: "Kate Moore", role: "CEO", status: "Active"}, {email: "john@acme.com", id: 2, name: "John Smith", role: "CTO", status: "Active"}, {email: "sara@acme.com", id: 3, name: "Sara Johnson", role: "CMO", status: "On Leave"}, {email: "michael@acme.com", id: 4, name: "Michael Brown", role: "CFO", status: "Active"}, { email: "emily@acme.com", id: 5, name: "Emily Davis", role: "Product Manager", status: "Inactive", }, {email: "davis@acme.com", id: 6, name: "Davis Wilson", role: "Lead Designer", status: "Active"}, { email: "olivia@acme.com", id: 7, name: "Olivia Martinez", role: "Frontend Engineer", status: "Active", }, { email: "james@acme.com", id: 8, name: "James Taylor", role: "Backend Engineer", status: "Active", }, ]; // --- TanStack 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: "Name"}), columnHelper.accessor("role", {header: "Role"}), columnHelper.accessor("status", { cell: (info) => ( {info.getValue()} ), header: "Status", }), columnHelper.accessor("email", {header: "Email"}), ]); // --- Sorting Bridge ------------------------------------------------------- // Convert TanStack SortingState → React Aria SortDescriptor function toSortDescriptor(sorting: SortingState): SortDescriptor | undefined { const first = sorting[0]; if (!first) return undefined; return { column: first.id, direction: first.desc ? "descending" : "ascending", }; } // Convert React Aria SortDescriptor → TanStack SortingState function toSortingState(descriptor: SortDescriptor): SortingState { return [{desc: descriptor.direction === "descending", id: descriptor.column as string}]; } // --- 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) => ( {({sortDirection}) => ( {flexRender(header.column.columnDef.header, header.getContext())} )} ))} {table.getRowModel().rows.map((row) => ( {row.getAllCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} ))} {start} to {end} of {users.length} results table.previousPage()} > Prev {pages.map((p) => ( table.setPageIndex(p - 1)} > {p} ))} table.nextPage()} > Next
); } ``` ### Custom Cells ```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: "Active" | "Inactive" | "On Leave"; email: string; } const statusColorMap: Record = { Active: "success", Inactive: "danger", "On Leave": "warning", }; const users: User[] = [ { email: "kate@acme.com", id: 4586932, image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg", name: "Kate Moore", role: "Chief Executive Officer", status: "Active", }, { email: "john@acme.com", id: 5273849, image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg", name: "John Smith", role: "Chief Technology Officer", status: "Active", }, { email: "sara@acme.com", id: 7492836, image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg", name: "Sara Johnson", role: "Chief Marketing Officer", status: "On Leave", }, { email: "michael@acme.com", id: 8293746, image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg", name: "Michael Brown", role: "Chief Financial Officer", status: "Active", }, { email: "emily@acme.com", id: 1234567, image_url: "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg", name: "Emily Davis", role: "Product Manager", status: "Inactive", }, ]; export function CustomCells() { const [selectedKeys, setSelectedKeys] = useState(new Set()); const [sortDescriptor, setSortDescriptor] = useState({ column: "name", direction: "ascending", }); const sortedUsers = useMemo(() => { return [...users].sort((a, b) => { const col = sortDescriptor.column as keyof User; const first = String(a[col]); const second = String(b[col]); let cmp = first.localeCompare(second); if (sortDescriptor.direction === "descending") { cmp *= -1; } return cmp; }); }, [sortDescriptor]); return ( {({sortDirection}) => ( Worker ID )} {({sortDirection}) => ( Member )} {({sortDirection}) => ( Role )} {({sortDirection}) => ( Status )} Actions {sortedUsers.map((user) => (
#{user.id.toString()}{" "}
{user.name .split(" ") .map((n) => n[0]) .join("")}
{user.name} {user.email}
{user.role} {user.status}
))}
); } ``` ## Customization ### Tailwind CSS ```tsx import {Table} from "@heroui/react"; const headerCell = "text-xs font-semibold uppercase tracking-wide text-neutral-500 dark:text-neutral-400"; const rowClass = "border-t border-border/60 transition-colors hover:bg-neutral-100/60 even:bg-neutral-50/50 dark:hover:bg-neutral-800/50 dark:even:bg-neutral-900/30"; export function CustomStyles() { return ( Name Role Status Kate Moore CEO Active John Smith CTO Active Sara Johnson CMO On leave
); } ``` ### Global CSS To customize the Table component classes, you can use the `@layer components` directive. [Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes). ```css @layer components { .table-root { @apply relative grid w-full overflow-clip; } .table__header { @apply bg-gray-100; } .table__column { @apply px-4 py-2.5 text-left text-xs font-medium text-gray-600; } .table__row { @apply bg-white border-b border-gray-200; } .table__cell { @apply px-4 py-3 text-sm; } .table__footer { @apply flex items-center px-4 py-2.5; } } ``` ## 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 Table component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/table.css)): #### Base Classes \[!toc] * `.table-root` - Root container (named `table-root` instead of `table` because `table` is a built-in Tailwind CSS utility class for `display: table`) * `.table__scroll-container` - Horizontal scroll wrapper with custom scrollbar * `.table__content` - The `` element * `.table__header` - Header row (``) * `.table__column` - Column header cell (``) * `.table__row` - Row element (``) * `.table__cell` - Data cell (`
`) * `.table__body` - Body section (`
`) * `.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 (
{(day) => {day}} {(date) => } Selected date: {value ? value.toString() : "(none)"}
); } ``` ### 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 (
{(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 (
{(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()}
); } ``` ### 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 ? ( ) : 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 ( {(segment) => } ); } ``` ## Anatomy ```tsx import {DateField, Label, Description, FieldError} from '@heroui/react'; export default () => ( ) ``` > **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 ( {(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) => } 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 (
{(segment) => } {(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 ( {(segment) => } Enter a date {(segment) => } Enter a date for your appointment ); } ``` ### With Description ```tsx "use client"; import {DateField, Description, Label} from "@heroui/react"; export function WithDescription() { return (
{(segment) => } Enter your date of birth {(segment) => } Enter a date for your appointment
); } ``` ### Required Field ```tsx "use client"; import {DateField, Description, Label} from "@heroui/react"; export function Required() { return (
{(segment) => } {(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 (
{(segment) => } This date field is disabled {(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 (
{(segment) => } {(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 (
{(segment) => } Please enter a valid 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 (
{(segment) => }

Determines the smallest unit displayed in the date picker. By default, this is "day" for dates, and "minute" for times.

); } ``` ### 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 (
{(segment) => } Current value: {value ? value.toString() : "(empty)"}
); } ``` ### 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 (
{(segment) => } {isInvalid ? ( Date must be today or in the future ) : ( Enter a date from today onwards )}
); } ``` ### 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 (
{(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 (
} >
}>
}> {(segment) => } ); } ``` ## Customization ### Tailwind CSS ```tsx "use client"; import {DateField, Label} from "@heroui/react"; export function CustomStyles() { return ( {(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'; {(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 ( {(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 () => ( ) ``` ## Examples ### Disabled ```tsx "use client"; import {Calendar, DateField, DatePicker, Description, Label} from "@heroui/react"; import {getLocalTimeZone, today} from "@internationalized/date"; export function Disabled() { return ( {(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 (
{(segment) => } {(day) => {day}} {(date) => } {({year}) => } Current value: {value ? value.toString() : "(empty)"}
); } ``` ### 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 ( {(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}) => ( <> {(segment) => } {(day) => {day}} {(date) => } {({year}) => } {!!showTimeField && (
state.setTimeValue(v as TimeValue)} > {(segment) => }
)}
)}
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 (
{(segment) => } {isInvalid ? ( Date must be today or in the future. ) : ( Choose a valid appointment date. )} {(day) => {day}} {(date) => } {({year}) => }
); } ``` ### Custom Indicator `DatePicker.TriggerIndicator` renders the default `IconCalendar` when no children are provided. Pass children to replace it. ```tsx "use client"; import {Calendar, DateField, DatePicker, Description, Label} from "@heroui/react"; import {Icon} from "@iconify/react"; export function WithCustomIndicator() { return ( {(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 (
} >
} >
}> {(segment) => ( } segment={segment} /> )}
); } ``` ### 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 ( {(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}) => ( <> {(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) => }
)} Selected:{" "} {state.value && state.value.start && state.value.end ? formatDate({end: state.value.end, start: state.value.start}) : "No date selected"}
)}
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 (
{(segment) => } {(segment) => } {isInvalid ? ( Please choose a valid range in the future. ) : ( Select your check-in and check-out dates. )} {(day) => {day}} {(date) => } {({year}) => }
); } ``` ### Custom Indicator `DateRangePicker.TriggerIndicator` renders the default `IconCalendar` when no children are provided. Pass children to replace it. ```tsx "use client"; import {DateField, DateRangePicker, Description, Label, RangeCalendar} from "@heroui/react"; import {Icon} from "@iconify/react"; export function WithCustomIndicator() { return ( {(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" > {(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 ( {(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 ( {(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 (
{(day) => {day}} {(date) => } Selected range: {value ? `${value.start.toString()} -> ${value.end.toString()}` : "(none)"}
); } ``` ### 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 (
{(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 (
{(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()}
); } ``` ### 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 ? ( ) : 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 ( {(segment) => } ); } ``` ## Anatomy ```tsx import {TimeField, Label, Description, FieldError} from '@heroui/react'; export default () => ( ) ``` > **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 ( {(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) => } 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 ( {(segment) => } Enter a time {(segment) => } Enter a time for your appointment ); } ``` ### With Description ```tsx "use client"; import {Description, Label, TimeField} from "@heroui/react"; export function WithDescription() { return (
{(segment) => } Enter the start time {(segment) => } Enter the end time
); } ``` ### Required Field ```tsx "use client"; import {Description, Label, TimeField} from "@heroui/react"; export function Required() { return (
{(segment) => } {(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 (
{(segment) => } This time field is disabled {(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 (
{(segment) => } {(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 (
{(segment) => } Please enter a valid 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 (
{(segment) => } Current value: {value ? value.toString() : "(empty)"}
); } ``` ### 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