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.
npx shadcn@latest add @velaui/radio-grouppnpm dlx velaui add @velaui/radio-groupyarn dlx shadcn@latest add @velaui/radio-groupbunx --bun shadcn@latest add @velaui/radio-groupveni add radio-groupInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/radio-grouppnpm dlx velaui add @velaui/radio-groupyarn dlx shadcn@latest add @velaui/radio-groupbunx --bun shadcn@latest add @velaui/radio-groupExamples
- Basic
- Disabled
- Size
- Description
- Invalid
- Choice card
- Choice card price
- Choice card group
Usage
Props
RadioGroup
Takes every prop of RadioGroupProps.
RadioGroupItem
| Prop | Type | Default |
|---|---|---|
size | "sm" | "md" | "lg" | "md" |
Takes these and every prop of RadioGroupItemProps.
RadioGroupField
| Prop | Type | Default |
|---|---|---|
size | "sm" | "md" | "lg" | "md" |
Takes these and every prop of RadioGroupFieldProps.
Dependencies
- npm
class-variance-authority@0.7.1radix-ui@1.6.0- Installs with it
- nothing else
- npm
@base-ui/react@1.6.0class-variance-authority@0.7.1- Installs with it
- nothing else
Source
components/ui/radio-group.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
export type RadioGroupProps = React.ComponentProps<typeof RadioGroupPrimitive.Root>;
function RadioGroup({
className,
...props
}: RadioGroupProps) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid w-full gap-2", className)}
{...props}
/>
);
}
const radioGroupItemVariants = cva(
// Focus and alert-focus are drawn as a 2px halo OUTSIDE the control (the box
// measures the same in every state), so they are a ring, not a border width.
// Checked draws no stroke — the border stays reserved but transparent.
"group/radio-group-item peer relative flex aspect-square shrink-0 rounded-full border-[1.5px] border-border-default bg-transparent outline-none transition-colors after:absolute after:-inset-x-3 after:-inset-y-2 not-data-checked:hover:border-border-hover not-data-checked:hover:bg-bg-surface-1 focus-visible:ring-2 not-data-checked:focus-visible:ring-ring-neutral-outline data-checked:focus-visible:ring-border-default not-data-checked:focus-visible:bg-bg-main disabled:pointer-events-none disabled:cursor-not-allowed not-data-checked:disabled:border-border-disabled not-data-checked:disabled:bg-input-disabled not-data-checked:aria-invalid:border-negative-middle aria-invalid:focus-visible:ring-negative data-checked:border-transparent data-checked:bg-bg-foreground data-checked:hover:bg-primary-hover data-checked:disabled:bg-bg-disabled data-checked:aria-invalid:bg-negative-middle",
{
variants: {
size: {
sm: "size-4",
md: "size-[18px]",
lg: "size-[22px]",
},
},
defaultVariants: {
size: "md",
},
},
);
const radioGroupIndicatorVariants = cva(
"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-bg-main group-disabled/radio-group-item:bg-bg-surface-2",
{
variants: {
size: {
sm: "size-1.5",
md: "size-1.75",
lg: "size-2",
},
},
defaultVariants: {
size: "md",
},
},
);
export type RadioGroupItemProps = React.ComponentProps<
typeof RadioGroupPrimitive.Item
> &
VariantProps<typeof radioGroupItemVariants>;
function RadioGroupItem({
className,
size = "md",
...props
}: RadioGroupItemProps) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
data-size={size}
className={cn(radioGroupItemVariants({ size }), className)}
{...props}
>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="flex items-center justify-center"
>
<span className={cn(radioGroupIndicatorVariants({ size }))} />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}
const radioGroupFieldVariants = cva(
"flex w-fit items-center gap-2 text-sm leading-[1.4] font-normal text-text-muted has-disabled:cursor-not-allowed has-disabled:text-text-disabled",
{
variants: {
size: {
sm: "h-5",
md: "h-5",
lg: "h-5.5",
},
},
defaultVariants: {
size: "md",
},
},
);
export type RadioGroupFieldProps = React.ComponentProps<"label"> &
VariantProps<typeof radioGroupFieldVariants>;
function RadioGroupField({
className,
size = "md",
...props
}: RadioGroupFieldProps) {
return (
<label
data-slot="radio-group-field"
data-size={size}
className={cn(radioGroupFieldVariants({ size }), className)}
{...props}
/>
);
}
export {
RadioGroup,
RadioGroupItem,
RadioGroupField,
radioGroupItemVariants,
radioGroupFieldVariants,
};components/ui/radio-group.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
export type RadioGroupProps = React.ComponentProps<typeof RadioGroupPrimitive>;
function RadioGroup({
className,
...props
}: RadioGroupProps) {
return (
<RadioGroupPrimitive
data-slot="radio-group"
className={cn("grid w-full gap-2", className)}
{...props}
/>
);
}
const radioGroupItemVariants = cva(
// Focus and alert-focus are drawn as a 2px halo OUTSIDE the control (the box
// measures the same in every state), so they are a ring, not a border width.
// Checked draws no stroke — the border stays reserved but transparent.
// Base UI renders the item as a span, so disabled styling keys off
// data-disabled instead of :disabled.
"group/radio-group-item peer relative flex aspect-square shrink-0 rounded-full border-[1.5px] border-border-default bg-transparent outline-none transition-colors after:absolute after:-inset-x-3 after:-inset-y-2 not-data-checked:hover:border-border-hover not-data-checked:hover:bg-bg-surface-1 focus-visible:ring-2 not-data-checked:focus-visible:ring-ring-neutral-outline data-checked:focus-visible:ring-border-default not-data-checked:focus-visible:bg-bg-main data-disabled:pointer-events-none data-disabled:cursor-not-allowed not-data-checked:data-disabled:border-border-disabled not-data-checked:data-disabled:bg-input-disabled not-data-checked:aria-invalid:border-negative-middle aria-invalid:focus-visible:ring-negative data-checked:border-transparent data-checked:bg-bg-foreground data-checked:hover:bg-primary-hover data-checked:data-disabled:bg-bg-disabled data-checked:aria-invalid:bg-negative-middle",
{
variants: {
size: {
sm: "size-4",
md: "size-[18px]",
lg: "size-[22px]",
},
},
defaultVariants: {
size: "md",
},
},
);
const radioGroupIndicatorVariants = cva(
"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-bg-main group-data-disabled/radio-group-item:bg-bg-surface-2",
{
variants: {
size: {
sm: "size-1.5",
md: "size-1.75",
lg: "size-2",
},
},
defaultVariants: {
size: "md",
},
},
);
export type RadioGroupItemProps = RadioPrimitive.Root.Props &
VariantProps<typeof radioGroupItemVariants>;
function RadioGroupItem({
className,
size = "md",
...props
}: RadioGroupItemProps) {
return (
<RadioPrimitive.Root
data-slot="radio-group-item"
data-size={size}
className={cn(radioGroupItemVariants({ size }), className)}
{...props}
>
<RadioPrimitive.Indicator
data-slot="radio-group-indicator"
className="flex items-center justify-center"
>
<span className={cn(radioGroupIndicatorVariants({ size }))} />
</RadioPrimitive.Indicator>
</RadioPrimitive.Root>
);
}
const radioGroupFieldVariants = cva(
"flex w-fit items-center gap-2 text-sm leading-[1.4] font-normal text-text-muted has-disabled:cursor-not-allowed has-disabled:text-text-disabled",
{
variants: {
size: {
sm: "h-5",
md: "h-5",
lg: "h-5.5",
},
},
defaultVariants: {
size: "md",
},
},
);
export type RadioGroupFieldProps = React.ComponentProps<"label"> &
VariantProps<typeof radioGroupFieldVariants>;
function RadioGroupField({
className,
size = "md",
...props
}: RadioGroupFieldProps) {
return (
<label
data-slot="radio-group-field"
data-size={size}
className={cn(radioGroupFieldVariants({ size }), className)}
{...props}
/>
);
}
export {
RadioGroup,
RadioGroupItem,
RadioGroupField,
radioGroupItemVariants,
radioGroupFieldVariants,
};