Single Choice
A single-select field with radio semantics, customizable indicators, badge support, a compact cells variant, and optional deselect.
Demo
Preferred framework
Choose one
Installation
pnpm dlx shadcn@latest add @turbopills-ui/single-choicenpx shadcn@latest add @turbopills-ui/single-choiceyarn dlx shadcn@latest add @turbopills-ui/single-choicebunx shadcn@latest add @turbopills-ui/single-choiceUsage
tsx1import { SingleChoice } from "@/components/turbopills/ui/single-choice"2import type { ChoiceOption } from "@/components/turbopills/ui/types"
tsx1const options: ChoiceOption[] = [2 { value: "initial", label: "Initial consultation" },3 { value: "followup", label: "Follow-up visit" },4 { value: "urgent", label: "Urgent care" },5]67export function AppointmentType() {8 const [value, setValue] = React.useState("")910 return (11 <SingleChoice12 title="Appointment type"13 options={options}14 value={value}15 onChange={setValue}16 />17 )18}
Examples
Cells Variant
Use variant="cells" for a compact grid of equally-sized cells — great for icons, emojis, or short-label options:
Choose your mood
Allow Deselect
Enable allowDeselect to let users click a selected option again to clear the selection:
Gender
Click again to deselect
Radio Variant
Use indicatorVariant="radio" for a traditional radio button. Combine with indicatorPosition="left" to place it before the label:
Appointment type
Numbers & Badges
Combine showNumbers with badge-enabled options:
Choose your plan
Props
SingleChoiceProps
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Optional heading displayed above the options. |
hint | string | — | Optional hint text shown below the title. |
options | ChoiceOption[] | — | Required. Array of options to render. |
value | string | — | Currently selected option value (controlled). |
onChange | (value: string) => void | — | Callback fired when the selection changes. |
variant | "default" | "cells" | "default" | Layout variant. "cells" renders a compact grid of equally-sized square cells. |
itemsPerRow | number | — | Number of columns in the grid. Defaults to options.length for "cells" variant. |
disabled | boolean | false | Disables the entire field. |
allowDeselect | boolean | false | Allows clicking the selected option again to clear the selection. |
indicatorPosition | "left" | "right" | "right" | Where to render the selection indicator relative to the label. |
indicatorVariant | "radio" | "icon" | "none" | "icon" | Visual style of the indicator. "icon" shows a filled circle-check, "radio" shows a standard radio button, "none" hides the indicator (always "none" for "cells" variant). |
showNumbers | boolean | false | Displays a 1-based index badge next to each option (hidden in "cells" variant). |
highlightOnHover | boolean | true | Applies a hover highlight to option rows. |
showOutlineOnSelect | boolean | true | Shows a border outline on the selected option. |
showFillOnSelect | boolean | false | Fills the background of the selected option. |
showShadowOnSelect | boolean | false | Adds a shadow to the selected option. |
allowTextSelection | boolean | false | Allows selecting text inside option rows. Disabled by default to prevent accidental highlighting during repeated clicks. |
onOptionClick | (value: string) => void | — | Callback fired when any option is clicked, regardless of selection change. |
className | string | — | Additional class names for the root container. |
ChoiceOption
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Required. Unique identifier for the option. |
label | React.ReactNode | — | Required. Text or element displayed as the option label. |
badge | string | — | Optional badge text rendered next to the label (hidden in "cells" variant). |
disabled | boolean | false | Disables this individual option. |
none | boolean | false | Marks this option as exclusive — selecting it deselects all others, and selecting any other deselects this one. |