Radio Group

A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.

Examples

Basic

PropDefaultTypeDescription
defaultValue-stringThe value of the radio item that should be checked when initially rendered.
disabledfalsebooleanWhen true, prevents the user from interacting with radio items.
modelValue-stringThe controlled value of the radio item to check. Can be binded as v-model.
items-RadioGroupItem[]The radio items to render.
valueKeyvaluestringThe key of the radio item to use for the value attribute.
labelKeylabelstringThe key of the radio item to use for the label attribute.
descriptionKeydescriptionstringThe description of the radio item to use for the description attribute.
RadioGroupItem PropDefaultTypeDescription
value-stringThe value of the radio item.
disabledfalsebooleanWhen true, prevents the user from interacting with the radio item.
label-stringThe label of the radio item.
icon-stringThe icon of the radio item.
description-stringThe description of the radio item.
Preview
Code

For bright environments

Easier on the eyes

Follows your system preference

Orientation

PropDefaultTypeDescription
orientationverticalvertical, horizontalThe orientation of the radio group items. Controls the layout direction.
Preview
Code
Vertical (Default)

Horizontal

Color

PropDefaultTypeDescription
radio-groupprimary{color}Set the color of the radio-group.
item.radioGroup-{color}Set the color of the specific radio item.
Preview
Code

Size and Square

PropDefaultTypeDescription
sizemdstringSets the size of the radio-group.
square2.5remstringSets the radio-group to a square shape with specified dimensions. This does not affect the size of the fallback value.
item.sizemdstringSets the size of the specific radio item.
item.square2.5remstringSets the specific radio item to a square shape with specified dimensions. This does not affect the size of the fallback value.

🚀 Adjust radio-group size freely using any size, breakpoints (e.g., sm:sm, xs:lg), or states (e.g., hover:lg, focus:3xl).

Preview
Code

Icon

PropDefaultTypeDescription
icon-stringThe icon to render.
item.icon-stringThe icon to render for the specific item.
Preview
Code

Rounded

PropDefaultTypeDescription
roundedmdstringSet the radio-group to be rounded.
item.roundedmdstringSet the specific radio item to be rounded.

🚀 Adjust radio-group rounded freely using any value, breakpoints (e.g., sm:sm, xs:lg), or states (e.g., hover:lg, focus:full).

Preview
Code

Slots

NamePropsDescription
defaultitemsThe item slot.
label-The label slot.
icon-The icon slot.
Preview
Code

Presets

shortcuts/radio-group.ts
import type { RuleContext } from '@unocss/core'
import type { Theme } from '@unocss/preset-uno'
import { parseColor } from '@unocss/preset-mini/utils'

type RadioGroupPrefix = 'radio-group'

export const staticRadioGroup: Record<`${RadioGroupPrefix}-${string}` | RadioGroupPrefix, string> = {
  // configurations
  'radio-group': 'gap-2 flex flex-wrap',
  'radio-group-orientation-vertical': 'flex-col',
  'radio-group-orientation-horizontal': 'flex-row',

  // components
  'radio-group-item-root': 'flex flex-col',
  'radio-group-item-wrapper': 'flex items-center gap-2',
  'radio-group-item': 'aspect-square rounded-full border border-brand text-brand shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-base disabled:n-disabled',
  'radio-group-item-label': 'text-0.875em font-medium',
  'radio-group-item-description': 'text-0.875em text-muted ml-[calc(1.1em+0.5rem)]',

  'radio-group-indicator': 'flex items-center justify-center',
  'radio-group-indicator-icon-base': 'h-0.875em w-0.875em fill-brand',
  'radio-group-indicator-icon': 'i-dot',
}

export const dynamicRadioGroup = [
  [/^radio-group-(.*)$/, ([, body]: string[], { theme }: RuleContext<Theme>) => {
    const color = parseColor(body, theme)
    if ((color?.cssColor?.type === 'rgb' || color?.cssColor?.type === 'rgba') && color.cssColor.components)
      return `n-${body}-600 dark:n-${body}-500`
  }],
]

export const radioGroup = [
  ...dynamicRadioGroup,
  staticRadioGroup,
]

Props

types/radio-group.ts
import type { AcceptableValue, RadioGroupItemProps, RadioGroupRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

interface BaseProps {
  /**
   * The class name of the radio group.
   */
  class?: HTMLAttributes['class']
  /**
   * Set the size of the avatar.
   */
  size?: HTMLAttributes['class']
  /**
   * Set the height and width of the avatar.
   */
  square?: HTMLAttributes['class']
  /**
   * Set the border radius of the avatar.
   */
  rounded?: HTMLAttributes['class']
  /**
   * Update the icon of the radio.
   */
  icon?: HTMLAttributes['class']
}

export interface NRadioGroupProps<T extends AcceptableValue> extends BaseProps, RadioGroupRootProps {
  /**
   * The items to display in the radio group.
   */
  items?: T[] | NRadioGroupItemProps[]
  /**
   * The key name to use to display in the radio items.
   */
  labelKey?: keyof T
  /**
   * The key name to use to display in the selected value.
   */
  valueKey?: keyof T
  /**
   * The key name to use to display the description in the radio items.
   */
  descriptionKey?: keyof T
  /**
   * Switch the position of label and radio.
   *
   * @default false
   */
  reverse?: boolean
  /**
   * Allows you to add `UnaUI` radio preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/radio-group.ts
   * @example
   * radio-group="green"
   */
  radioGroup?: HTMLAttributes['class']
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/radio-group.ts
   */
  una?: NRadioGroupUnaProps
}

export interface NRadioGroupItemProps extends BaseProps, RadioGroupItemProps {
  /**
   * The label to display in the radio item.
   */
  label?: string
  /**
   * Allows you to add `UnaUI` radio preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/radio-group.ts
   * @example
   * radio-group="green"
   */
  radioGroup?: HTMLAttributes['class']
  /**
   * The key name to use to display in the selected value.
   */
  description?: string
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/radio-group.ts
   */
  una?: NRadioGroupUnaProps
}

export interface NRadioGroupUnaProps {
  radioGroup?: HTMLAttributes['class']

  radioGroupItemRoot?: HTMLAttributes['class']
  radioGroupItemWrapper?: HTMLAttributes['class']
  radioGroupItem?: HTMLAttributes['class']
  radioGroupItemLabel?: HTMLAttributes['class']
  radioGroupItemDescription?: HTMLAttributes['class']

  radioGroupIndicator?: HTMLAttributes['class']
  radioGroupIndicatorIcon?: HTMLAttributes['class']
}

Components

RadioGroup.vue
RadioGroupItem.vue
<script lang="ts">
import type { AcceptableValue, RadioGroupRootEmits } from 'reka-ui'
import type { NRadioGroupItemProps, NRadioGroupProps } from '../../../types'
</script>

<script setup lang="ts" generic="T extends AcceptableValue">
import { reactivePick } from '@vueuse/core'
import { RadioGroupRoot, useForwardPropsEmits } from 'reka-ui'
import { cn } from '../../../utils'
import RadioGroupItem from './RadioGroupItem.vue'

const props = withDefaults(defineProps<NRadioGroupProps<T>>(), {
  radioGroup: 'primary',
  size: 'md',
  square: '1em',
})

const emits = defineEmits<RadioGroupRootEmits>()

const rootProps = reactivePick(props, [
  'modelValue',
  'defaultValue',
  'disabled',
  'required',
  'loop',
])
const forwarded = useForwardPropsEmits(rootProps, emits)

function isRadioGroupItem(item: any): item is NRadioGroupItemProps {
  return typeof item === 'object' && item !== null && 'value' in item
}

function isObjectWithKey<K extends PropertyKey>(item: unknown, key: K): item is Record<K, unknown> {
  return typeof item === 'object' && item !== null && key in item
}

function getItemValue(item: NonNullable<T | NRadioGroupItemProps>): string {
  if (props.valueKey && isObjectWithKey(item, props.valueKey))
    return String(item[props.valueKey])

  if (isRadioGroupItem(item))
    return String(item.value)

  return String(item)
}

function getItemLabel(item: NonNullable<T | NRadioGroupItemProps>): string {
  if (props.labelKey && isObjectWithKey(item, props.labelKey))
    return String(item[props.labelKey])

  if (isRadioGroupItem(item) && item.label)
    return String(item.label)

  return ''
}

function getItemDescription(item: NonNullable<T | NRadioGroupItemProps>): string | undefined {
  if (props.descriptionKey && isObjectWithKey(item, props.descriptionKey))
    return String(item[props.descriptionKey])

  if (isRadioGroupItem(item) && item.description)
    return String(item.description)

  return undefined
}
</script>

<template>
  <RadioGroupRoot
    :class="cn(
      'radio-group',
      orientation === 'horizontal' ? 'radio-group-orientation-horizontal' : 'radio-group-orientation-vertical',
      una?.radioGroup,
      props.class,
    )"
    v-bind="forwarded"
  >
    <slot>
      <template v-if="items?.length">
        <RadioGroupItem
          v-for="item in items"
          :key="getItemValue(item!)"
          v-bind="{ rounded, icon, size, square, radioGroup, ...(item as NRadioGroupItemProps) }"
          :una="{ ...una, ...(isRadioGroupItem(item!) && item!.una) }"
          :value="getItemValue(item!)"
        >
          <slot
            name="label"
            :item
          >
            {{ getItemLabel(item!) }}
          </slot>

          <template #description>
            <slot
              name="description"
              :item
            >
              {{ getItemDescription(item!) }}
            </slot>
          </template>
        </RadioGroupItem>
      </template>
    </slot>
  </RadioGroupRoot>
</template>