Component API

<radio-cards>

Open live example

Single-select group of full-width cards, each with a label and optional
description — for a handful of meaningfully different choices where the
description matters. For many short, same-shaped options (a color swatch,
a basemap style), use radio-pills instead. Wraps native radio inputs for
keyboard/a11y and fires change rather than relying on form submission.

An unchecked card is treated as ui-button's secondary variant: it reads
the same --ui-button-secondary-background/-hover and
-border/-hover tokens, so a gradient theme applies to a card exactly
like it does to any button. The selected card's border/radio dot instead
read --ui-button-accent (a solid stand-in, since border-color/
accent-color can't render a gradient), and its background reads the
shared --ui-button-secondary-surface-muted plus --ui-button-highlight,
so a gradient theme tints it consistently with button-group/
pagination-nav's equivalents. That shared value is tuned for small
controls, so a card — with far more area — blends it 45% toward
--ui-surface rather than taking a second token: the tint stays the same
hue and gradient, just lighter on light themes (and correspondingly
deeper on dark ones) than on a button.

layout controls how cards flow ("mixed" wraps with per-option
fullWidth rows, "vertical" stacks one per row, "horizontal" stays
side-by-side at every width). hideInput visually hides the radio dot via
the same sr-only clip pattern as ui-checkbox — the native input stays
in the DOM for keyboard/a11y, and the card's own border/tint carries the
selected state instead.

Install

import "@f-ewald/components/radio-cards.js";

Usage

<radio-cards></radio-cards>
<script type="module">
  const el = document.querySelector("radio-cards");
  el.options = [
    { value: "simple", label: "Simple", description: "Quick-ranking view" },
    { value: "detailed", label: "Detailed", description: "Every section and layer" },
  ];
  el.value = "simple";
  el.addEventListener("change", (e) => console.log(e.detail.value));
</script>

Attributes / properties

PropertyAttributeTypeDefaultDescription
optionsJS property onlyRadioCardOption[][]Options to render, one card each.
valuevaluestring""Currently selected value.
disableddisabledbooleanfalseDisables every native radio in the group.
layoutlayout"vertical" | "horizontal" | "mixed""mixed"How the cards flow. - "mixed" (default): cards wrap onto as many rows as fit; options marked fullWidth take a row of their own. - "vertical": one full-width card per row, never side by side. - "horizontal": every card on one row, equal widths, no wrapping.
hideInputhide-inputbooleanfalseVisually hides the radio dot; the card's own border/tint carries the selected state.

Events

EventDescription
changeA card was selected; detail: { value }.

Slots

None.

CSS custom properties

Custom property
--ui-border
--ui-border-width
--ui-button-accent
--ui-button-highlight
--ui-button-secondary-background
--ui-button-secondary-background-hover
--ui-button-secondary-border
--ui-button-secondary-border-hover
--ui-button-secondary-surface-muted
--ui-focus-ring
--ui-font
--ui-font-size-sm
--ui-font-weight-regular
--ui-font-weight-semibold
--ui-primary
--ui-radius-sm
--ui-surface
--ui-surface-muted
--ui-text
--ui-text-muted