- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
"use client"
import * as React from "react"The ChoiceTile component turns each option of a radio or checkbox group into a whole pressable tile with a title, an optional description, leading art and a trailing value such as a price. Use it when an option needs more than a word: transfer speed with a fee, a plan with a price, a recipient with a handle. Pick RadioGroup or Checkbox for plain form options, and ChipGroup for short options in one row.
Installation#
pnpm dlx shadcn@latest add @dots/choice-tile
Usage#
import { ChoiceTile, ChoiceTileGroup } from "@/components/ui/choice-tile"<ChoiceTileGroup label="Transfer speed" defaultValue="standard">
<ChoiceTile value="standard" title="Standard" description="1–3 business days" trailing="Free" />
<ChoiceTile value="instant" title="Instant" description="In a few seconds" trailing="$3.00" />
</ChoiceTileGroup>Composition#
Single choice:
ChoiceTileGroup
└── ChoiceTileMultiple choice:
ChoiceTileCheckboxGroup
└── ChoiceTileCheckboxA ChoiceTileCheckbox can also stand alone as a single toggle.
Features#
- Single choice on React Aria
RadioGroup, multiple choice onCheckboxGroup - Two looks:
fillinverts the selected tile,indicatoradds a round or square control at the end - Title, badge, description, leading art, a trailing value and extra content under the text
- Rows, or a grid of stacked tiles with 1–4 columns per breakpoint
- Group label, description and error message, with validation from React Aria
- A
nestedsurface for tiles inside a card, sheet or dialog - Look props set on the group apply to every tile unless a tile overrides them
Indicator#
variant="fill" (the default) suits short options. Use variant="indicator" for richer options with a description or extra details: a round control sits at the end and the selected tile only steps its fill up. Children render as extra content under the text row.
"use client"
import { ChoiceTile, ChoiceTileGroup } from "@/components/ui/choice-tile"Badge#
Pass a Badge with variant="raised" to badge to call out a saving or a recommended option. On a selected fill tile, neutral badges invert to stay readable.
"use client"
import { Badge } from "@/components/ui/badge"Leading#
Use leading for an avatar, flag, logo or icon before the text.
"use client"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"Stack#
Set layout="stack" to stand tiles in a grid. The default is two columns.
"use client"
import { ChoiceTile, ChoiceTileGroup } from "@/components/ui/choice-tile"Columns#
Set columns to a count from 1 to 4, or to { base, sm, lg } to change the count from the sm and lg breakpoints up.
"use client"
import { ChoiceTile, ChoiceTileGroup } from "@/components/ui/choice-tile"Multiple#
Use ChoiceTileCheckboxGroup with ChoiceTileCheckbox for add-ons or services where several can be on. Checkbox tiles default to the indicator variant with a square check.
1 selected
"use client"
import * as React from "react"Nested#
Set surface="nested" when the group sits inside a card, sheet or dialog.
"use client"
import { Card, CardContent } from "@/components/ui/card"Invalid#
Set isInvalid (or isRequired with form validation) and an errorMessage. To bring people back to the group, focus its first enabled input through ref.
"use client"
import * as React from "react"groupRef.current
?.querySelector<HTMLInputElement>("input:not(:disabled)")
?.focus()Disabled#
Set isDisabled on a tile to rule out one option and say why in its description, or on the group to disable every tile.
"use client"
import { ChoiceTile, ChoiceTileGroup } from "@/components/ui/choice-tile"Accessibility#
Each tile is a React Aria Radio or Checkbox: the tile is the <label> of a real, visually hidden input. React Aria handles the group role, arrow keys between radio tiles, Space to toggle, required and invalid states, and the link between the error message and the group.
Name the group#
Give the group a visible label, or an aria-label when there is none. A group description is read with every tile.
What is read for each tile#
title and trailing form the tile's accessible name, so "Instant, $3.00" is read as one option. badge, description and extra content are kept out of the name and linked as the description instead. Keep the title short and put details in the description.
Meaning beyond color#
The fill variant shows the selection by color alone. Prefer variant="indicator" when tiles sit on a background with low contrast, and keep the reason for a disabled tile in its description.
API Reference#
ChoiceTileGroup#
Single choice between tiles. Accepts all React Aria RadioGroup props.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | null | - | The controlled selected value. |
defaultValue | string | null | - | The initial value when uncontrolled. |
onChange | (value: string) => void | - | Called when the selection changes. |
label | React.ReactNode | - | Visible label of the group. |
description | React.ReactNode | - | One line under the label, read with every tile. |
errorMessage | React.ReactNode | - | Shown under the tiles while invalid. |
isInvalid | boolean | false | Marks the group invalid. |
isRequired | boolean | false | Requires a choice. |
isDisabled | boolean | false | Disables every tile. |
variant | "fill" | "indicator" | "fill" | Look of every tile. |
layout | "row" | "stack" | "row" | Tiles in rows, or stacked in a grid. |
columns | 1 | 2 | 3 | 4 | { base, sm?, lg? } | 2 | Grid columns when layout="stack". |
surface | "default" | "nested" | "default" | "nested" inside a card, sheet or dialog. |
ref | React.Ref<HTMLDivElement> | - | The group element, e.g. to focus a tile after validation. |
className | string | - | Additional classes to apply to the group. |
ChoiceTile#
One option of a ChoiceTileGroup. Accepts all React Aria Radio props.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | The value reported when this tile is selected. |
title | React.ReactNode | - | Required. The option itself. Part of the accessible name. |
badge | React.ReactNode | - | A Badge next to the title. Read as part of the description. |
description | React.ReactNode | - | One quiet line under the title. |
leading | React.ReactNode | - | Avatar, flag, logo or icon before the text. |
trailing | React.ReactNode | - | Price or value at the end. Part of the accessible name. |
children | React.ReactNode | - | Extra non-interactive content under the text row. |
isDisabled | boolean | false | Disables this tile. |
variant | "fill" | "indicator" | from group, "fill" | Overrides the group look. |
layout | "row" | "stack" | from group, "row" | Overrides the group layout. |
surface | "default" | "nested" | from group, "default" | Overrides the group surface. |
className | string | - | Additional classes to apply to the tile. |
ChoiceTileCheckboxGroup#
Multiple choice between tiles. Accepts all React Aria CheckboxGroup props, and the same label, description, errorMessage, variant, layout, columns, surface, ref and className props as ChoiceTileGroup.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string[] | - | The controlled selected values. |
defaultValue | string[] | - | The initial values when uncontrolled. |
onChange | (value: string[]) => void | - | Called when the selection changes. |
ChoiceTileCheckbox#
A tile that toggles on its own or as one option of a ChoiceTileCheckboxGroup. Accepts all React Aria Checkbox props and the same content and look props as ChoiceTile.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | The value reported inside a group. |
isSelected | boolean | - | The controlled state when used on its own. |
defaultSelected | boolean | - | The initial state when used on its own. |
onChange | (isSelected: boolean) => void | - | Called when the state changes. |
variant | "fill" | "indicator" | from group, "indicator" | "indicator" shows a square check. |