Skip to content
  1. Keyline UI
  2. Radio group
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Radio group

One choice of a few, as framed rows with room for a description and a price. The chosen row is ink, and the ink stretches to the next choice, leading edge first.

Loading the live example…

"use client"

import { useState } from "react"
import { RadioGroup, RadioGroupItem } from "@/components/keyline/ui/radio-group"

const OPTIONS = [
  { value: "truck", label: "Darryl's Thursday route", description: "Arrives Thu 12 Nov, before noon", meta: "Free", note: "On Thursday's truck." },
  { value: "morning", label: "Next morning", description: "A courier from the Wilkes-Barre depot", meta: "$25", note: "Courier booked for 8 AM tomorrow." },
  { value: "pickup", label: "Customer pickup", description: "Ready at reception from 3 PM today", meta: "Free", note: "Pam will have it at reception." },
]

export function DeliveryChoice() {
  const [how, setHow] = useState("truck")

  return (
    <div className="grid max-w-[480px] gap-3">
      <p id="delivery-label" className="m-0 text-[14px] font-semibold">
        Delivery for <span className="font-kl-mono">DM-4142</span>
      </p>
      <RadioGroup aria-labelledby="delivery-label" value={how} onValueChange={setHow}>
        {OPTIONS.map((o) => (
          <RadioGroupItem key={o.value} value={o.value} label={o.label} description={o.description} meta={o.meta} />
        ))}
      </RadioGroup>
      <p role="status" className="m-0 text-[14px] text-kl-ink-2">
        {OPTIONS.find((o) => o.value === how)?.note}
      </p>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/radio-group

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

import { RadioGroup, RadioGroupItem } from "@/components/keyline/ui/radio-group"
<RadioGroup aria-label="Delivery" value={how} onValueChange={setHow}>
  <RadioGroupItem value="truck" label="Thursday route" meta="Free" />
  <RadioGroupItem value="morning" label="Next morning" meta="" />
</RadioGroup>

Examples

In a panel

With layout="cell" the rows share the panel's keylines. The panel header names the choice, so the group takes an aria-label.

Loading the live example…

"use client"

import { useState } from "react"
import { Panel, PanelBody, PanelHeader } from "@/components/keyline/ui/panel"
import { RadioGroup, RadioGroupItem } from "@/components/keyline/ui/radio-group"

const PAPER = [
  { value: "20", label: "20 lb copy", description: "The everyday sheet", price: 42 },
  { value: "24", label: "24 lb premium", description: "Brighter, for letters", price: 58 },
  { value: "110", label: "110 lb cardstock", description: "Menus and invitations", price: 96 },
]

export function PaperWeight() {
  const [weight, setWeight] = useState("24")
  const chosen = PAPER.find((p) => p.value === weight)!

  return (
    <Panel className="max-w-[440px]">
      <PanelHeader title="Paper for the Wilkes-Barre Library quote" />
      <RadioGroup layout="cell" aria-label="Paper weight" value={weight} onValueChange={setWeight}>
        {PAPER.map((p) => (
          <RadioGroupItem key={p.value} value={p.value} label={p.label} description={p.description} meta={`$${p.price}`} />
        ))}
      </RadioGroup>
      <PanelBody>
        <p role="status" className="m-0 text-[14px] text-kl-ink-2">
          12 cases at ${chosen.price} a case: <span className="tabular-nums">${(12 * chosen.price).toLocaleString("en-US")}</span>.
        </p>
      </PanelBody>
    </Panel>
  )
}

Props

PropTypeDefaultDescription
valuestringDefault —The chosen item, with onValueChange. Or start with defaultValue.
layout"control" | "cell"Default "control"cell drops the frame, so the rows sit in a panel.
namestringDefault —Submits the chosen value with a form.
labelReactNodeDefault —On RadioGroupItem: the row's name. Required.
descriptionReactNodeDefault —On RadioGroupItem: a line under the label, read out with it.
metaReactNodeDefault —On RadioGroupItem: a cell at the end, for a price or a count.

Name the group with aria-label or aria-labelledby. Tab enters at the chosen row and arrow keys move and choose. For a short choice on one line, Segmented fits better. Built on Base UI Radio Group.

Sheet 19 of 73PreviousCheckbox groupNextSwitch
On this page