ephemera

Radio Group

Pencil circles that fill with a blot of ink.

Install

$ npx shadcn@latest add https://ephemera.kerrshift.com/r/radio-group.json

Or copy the source below into components/ui/radio-group.tsx. Core components need the Paper Theme and Radix where noted.

Usage

example.tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";

<RadioGroup defaultValue="post">
  <div className="flex items-center gap-3">
    <RadioGroupItem value="post" id="post" />
    <Label htmlFor="post">First-class post</Label>
  </div>
  <div className="flex items-center gap-3">
    <RadioGroupItem value="pigeon" id="pigeon" />
    <Label htmlFor="pigeon">Carrier pigeon</Label>
  </div>
</RadioGroup>

Built on the Radix Radio Group, so keyboard navigation and screen reader support come for free.

Props

PropTypeDefaultDescription
value / defaultValue / onValueChangestring—Selected option.
RadioGroupItem valuestring—The value this circle represents.
orientation"vertical" | "horizontal""vertical"Arrow-key direction.

Any other props are passed to the root element.

Fonts

Components read optional CSS variables and fall back to system fonts: --font-display, --font-receipt, --font-type, --font-hand and --font-stamp. This site uses Fraunces, IBM Plex Mono, Courier Prime, Caveat and Anton.

Source

components/ui/radio-group.tsx
"use client";

import * as React from "react";
import { RadioGroup as RadioGroupPrimitive } from "radix-ui";

/** Pencil circles that fill with a blot of ink. */
export function RadioGroup({ className = "", ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
  return <RadioGroupPrimitive.Root className={`grid gap-3 ${className}`} {...props} />;
}

export function RadioGroupItem({ className = "", ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
  return (
    <RadioGroupPrimitive.Item
      className={`peer relative inline-flex size-[20px] shrink-0 cursor-pointer items-center justify-center border-[1.5px] border-(--ink-muted) bg-(--paper-raised) outline-none transition-colors hover:border-(--ink) focus-visible:ring-2 focus-visible:ring-(--pen) focus-visible:ring-offset-2 focus-visible:ring-offset-(--paper) disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
      style={{ borderRadius: "50% 46% 52% 48% / 48% 52% 46% 50%" }}
      {...props}
    >
      <RadioGroupPrimitive.Indicator
        data-paper-motion
        className="block size-[10px] bg-(--pen) animate-[paper-blot_.25s_ease-out_both]"
        style={{ borderRadius: "55% 45% 50% 50% / 45% 55% 48% 52%" }}
      />
    </RadioGroupPrimitive.Item>
  );
}