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-choice

Usage

tsx
1import { SingleChoice } from "@/components/turbopills/ui/single-choice"
2import type { ChoiceOption } from "@/components/turbopills/ui/types"
tsx
1const options: ChoiceOption[] = [
2  { value: "initial", label: "Initial consultation" },
3  { value: "followup", label: "Follow-up visit" },
4  { value: "urgent", label: "Urgent care" },
5]
6 
7export function AppointmentType() {
8  const [value, setValue] = React.useState("")
9 
10  return (
11    <SingleChoice
12      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

PropTypeDefaultDescription
titlestringOptional heading displayed above the options.
hintstringOptional hint text shown below the title.
optionsChoiceOption[]Required. Array of options to render.
valuestringCurrently selected option value (controlled).
onChange(value: string) => voidCallback fired when the selection changes.
variant"default" | "cells""default"Layout variant. "cells" renders a compact grid of equally-sized square cells.
itemsPerRownumberNumber of columns in the grid. Defaults to options.length for "cells" variant.
disabledbooleanfalseDisables the entire field.
allowDeselectbooleanfalseAllows 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).
showNumbersbooleanfalseDisplays a 1-based index badge next to each option (hidden in "cells" variant).
highlightOnHoverbooleantrueApplies a hover highlight to option rows.
showOutlineOnSelectbooleantrueShows a border outline on the selected option.
showFillOnSelectbooleanfalseFills the background of the selected option.
showShadowOnSelectbooleanfalseAdds a shadow to the selected option.
allowTextSelectionbooleanfalseAllows selecting text inside option rows. Disabled by default to prevent accidental highlighting during repeated clicks.
onOptionClick(value: string) => voidCallback fired when any option is clicked, regardless of selection change.
classNamestringAdditional class names for the root container.

ChoiceOption

PropTypeDefaultDescription
valuestringRequired. Unique identifier for the option.
labelReact.ReactNodeRequired. Text or element displayed as the option label.
badgestringOptional badge text rendered next to the label (hidden in "cells" variant).
disabledbooleanfalseDisables this individual option.
nonebooleanfalseMarks this option as exclusive — selecting it deselects all others, and selecting any other deselects this one.