Getting Started
Components
- Accordion
- Advanced Toast
- AlertMessage
- ArrowButton
- Arrows
- Avatar
- Backdrop
- Basic Toast
- Breadcrumb
- Button
- CardBackground
- Card
- Carousel
- Chapters
- ChatBody
- ChatButton
- ChatHeader
- ChatInput
- ChatMessageActions
- ChatMessageEditor
- ChatMessage
- ChatTypingIndicator
- ChatWindowCsatPrompt
- ChatWindowQuote
- ChatWindow
- CheckboxSelect
- Checkbox
- Checkboxes
- Chip
- ChoiceExpander
- Clickable
- ContentReview
- Countdown
- CriteriaDates
- CriteriaParticipants
- CriteriaTransports
- Date Field
- Datepicker
- Dropdown
- ElasticHeight
- ExpandableCard
- ExpandableText
- Filter
- FloatingLabelTextField
- Form Control
- Form Label
- Frame
- HamburgerIcon
- Heading
- Image
- JourneyCard
- LayerHeader
- Layer
- Link
- Loader
- Number Field
- PageNavigation
- Pagination
- Paragraph
- Password
- Phone Field
- Popin
- Portal
- ProductThumbnail
- Promises
- Prose
- Radio Group
- RadioSelect
- Radio
- Range
- Scrollbar
- Select
- SidebarLayout
- SliderFrame
- SparkleWrapper
- Spinner
- Switch
- Table
- Tabs
- Tag
- TextField
- Toggle
- Tooltip
- ValidationMessage
Travel
Activities
'use client';
import { useState } from 'react';
import { RadioSelect } from '@/ui/forms/RadioSelect';
const statusOptions = [
{ value: 'open', label: 'Open' },
{ value: 'fixed', label: 'Fixed' },
{ value: 'in-progress', label: 'In Progress' },
{ value: 'closed', label: 'Closed' },
];
export function RadioSelectDemo() {
const [status, setStatus] = useState<string>('open');
const [category, setCategory] = useState<string>('');
return (
<div className="space-y-24" style={{ minWidth: 300 }}>
<RadioSelect
label="Status"
placeholder="Select a status..."
options={statusOptions}
value={status}
onChange={(_, v) => setStatus(v)}
/>
<RadioSelect
label="Category"
placeholder="Select a category..."
groups={[
{
label: 'Travel',
options: [
{ value: 'flight', label: 'Flight' },
{ value: 'hotel', label: 'Hotel' },
],
},
{
label: 'Activities',
options: [
{ value: 'ski', label: 'Ski' },
{ value: 'golf', label: 'Golf' },
{ value: 'spa', label: 'Spa' },
],
},
]}
value={category}
onChange={(_, v) => setCategory(v)}
/>
<RadioSelect
label="Priority"
options={[
{ value: 'high', label: 'High' },
{ value: 'medium', label: 'Medium' },
{ value: 'low', label: 'Low' },
]}
validationStatus="error"
errorMessage="Please select a priority"
/>
<RadioSelect label="Tags" options={statusOptions} value="open" disabled />
</div>
);
}
Installation
npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/radio-select.json
Usage
import { RadioSelect } from '@/ui/forms/RadioSelect';
export function Component() {
return (
<RadioSelect
label="Status"
options={[
{ value: 'open', label: 'Open' },
{ value: 'fixed', label: 'Fixed' },
]}
/>
);
}Examples
Flat options
The simplest usage — pass an options array and optionally a value to pre-select.
<RadioSelect
label="Status"
placeholder="Select a status..."
options={[
{ value: 'open', label: 'Open' },
{ value: 'fixed', label: 'Fixed' },
{ value: 'in-progress', label: 'In Progress' },
{ value: 'closed', label: 'Closed' },
]}
value="open"
/>Controlled
Manage the selected value in external state with onChange.
function Controlled() {
const [value, setValue] = useState('open');
return (
<RadioSelect
label="Status"
options={[
{ value: 'open', label: 'Open' },
{ value: 'fixed', label: 'Fixed' },
{ value: 'in-progress', label: 'In Progress' },
{ value: 'closed', label: 'Closed' },
]}
value={value}
onChange={(_, v) => setValue(v)}
/>
);
}Grouped options
Use groups instead of options to render options under labeled sections.
<RadioSelect
label="Category"
placeholder="Select a category..."
groups={[
{
label: 'Travel',
options: [
{ value: 'flight', label: 'Flight' },
{ value: 'hotel', label: 'Hotel' },
],
},
{
label: 'Activities',
options: [
{ value: 'ski', label: 'Ski' },
{ value: 'golf', label: 'Golf' },
{ value: 'spa', label: 'Spa' },
],
},
]}
/>Custom trigger label
Use renderValue to display a custom string in the trigger instead of the option label.
<RadioSelect
label="Priority"
options={[
{ value: 'high', label: 'High priority' },
{ value: 'medium', label: 'Medium priority' },
{ value: 'low', label: 'Low priority' },
]}
value="high"
renderValue={(val) => `Priority: ${val}`}
/>Validation states
Pass validationStatus and errorMessage to indicate an error or success.
<RadioSelect
label="Priority"
options={priorityOptions}
validationStatus="error"
errorMessage="Please select a priority"
/>
<RadioSelect
label="Priority"
options={priorityOptions}
value="high"
validationStatus="success"
/>Disabled
<RadioSelect
label="Status"
options={statusOptions}
value="open"
disabled
/>Behaviour
- Opening the dropdown shows all options as radio buttons.
- Selecting an option updates the trigger label and closes the dropdown immediately — unlike
CheckboxSelect, only one value can be active at a time. - Clicking outside the open panel closes it without changing the selection.
- The trigger inherits the same pill-shaped border styling as
TextFieldandSelect. - When no value is selected the placeholder text is shown in muted grey.
Accessibility
- The trigger has
aria-haspopup="listbox"andaria-expandedto communicate its state to screen readers. - The panel uses
role="listbox"and is hidden viaaria-hiddenwhen closed. - Each option is a native
<input type="radio">with an associated<label>, giving full keyboard and screen-reader support. - The component is disabled at the native level (
disabledon the<button>and each<input>), so it is excluded from the tab order automatically.
API Reference
RadioSelect
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | undefined | Label displayed above the trigger |
description | string | undefined | Secondary description shown next to the label |
placeholder | string | "Select..." | Text shown in the trigger when no value is selected |
options | RadioSelectOption[] | undefined | Flat list of options. Use groups instead for grouped options |
groups | RadioSelectGroup[] | undefined | Grouped options with optional group labels |
value | Value | undefined | Controlled selected value |
onChange | (event: Event, value: Value) => void | undefined | Called with the event and new value when an option is selected |
renderValue | (selected: Value) => ReactNode | undefined | Custom renderer for the trigger label when a value is selected |
disabled | boolean | false | Disables the trigger and all radio buttons |
required | boolean | false | Marks the field as required |
hideRequiredStar | boolean | undefined | Hides the required star even when required is true |
validationStatus | "default" | "error" | "success" | "default" | Visual validation state of the trigger |
errorMessage | string | undefined | Error text shown below when validationStatus is "error" |
className | string | undefined | Additional CSS classes on the outer wrapper |
RadioSelectOption
| Prop | Type | Description |
|---|---|---|
value | Value | The option's value |
label | ReactNode | Label shown in the panel |
RadioSelectGroup
| Prop | Type | Description |
|---|---|---|
label | ReactNode | Optional group heading |
options | RadioSelectOption[] | Options within this group |
Notes
- Pass
groupsinstead ofoptionsto render options under labeled sections. - Use
renderValueto customize the summary shown in the trigger (e.g."Priority: high"). - The dropdown closes automatically after a selection — this distinguishes
RadioSelectfromCheckboxSelect, where the panel stays open to allow multiple picks. optionsandgroupsare mutually exclusive; if both are provided,optionstakes precedence.