Inputs

Icon Radio Tiles

A radio group as icon tiles: a recognisable shape scans faster than a line of text, and the icons are inline SVG on currentColor with no icon package.

  • radio
  • icons
  • tiles
  • svg

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/radio-006?lang=en

Primary device
Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "radio-006" (Icon Radio Tiles) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/radio-006.json

Registry item: https://vibeui.ru/r/radio-006.json
Installs to: components/vibeui/radio-006.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A radio group as icon tiles: a recognisable shape scans faster than a line of text, and the icons are inline SVG on currentColor with no icon package.

Choice tiles with inline-SVG icons. One file, zero dependencies, a server component.

## 3. How to use it
import { Radio006 } from "@/components/vibeui/radio-006"

<Radio006
  legend="Primary device"
  defaultValue="phone"
/>

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-radio-006-* palette — do not swap it for your theme tokens
- the inline-SVG icons on currentColor: an icon package would break the one-file, zero-dependency rule
- the caption under every icon — an icon without a word is misread more often than you would expect
- aria-hidden on the svg: the accessible name comes from the text caption, not the drawing
- the radio hidden by clip-path but not disabled, together with the visible focus ring on the tile
- the component's own light surface — without it the dark text disappears on a dark page

## 6. You may change
- the group heading through legend
- the tiles, captions and icon paths through options
- the preselected tile through defaultValue
- the form field name through name

## 7. Rules
- Icons are paths in a 24×24 coordinate system: a path from another grid has to be rescaled.
- The stroke width is fixed at 1.6 — increase it for larger icons or the drawing looks washed out.
- Tiles stay equal in height only with short captions: a wrapped line stretches the whole grid row.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/radio-006.json
https://vibeui.ru/r/radio-006.json

Серверный компонент на fieldset с legend. Плитки разложены в grid с auto-fit, поэтому на узкой карточке они перестраиваются сами. Иконки — inline-SVG с fill:none и stroke:currentColor: цвет плитки красит и рисунок, и подпись одним правилом. Радио спрятано clip-path'ом, но остаётся в потоке фокуса; выбранная плитка меняет рамку, фон и цвет через :has(input:checked). Палитра в --vibeui-radio-006-*.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Radio006Option = {
  value: string
  label: string
  /** Путь иконки в системе координат 24×24. */
  path: string
}

export type Radio006Props = Omit<
  ComponentPropsWithoutRef<"fieldset">,
  "children" | "defaultValue"
> & {
  legend?: string
  options?: Radio006Option[]
  name?: string
  defaultValue?: string
  accent?: string
}

// Идея компонента: выбор по узнаваемой форме, а не по чтению. Плитки с
// иконками сканируются быстрее списка, поэтому подпись здесь вторична, а
// иконка занимает половину высоты. Иконки — inline-SVG с currentColor:
// пакет иконок потянул бы зависимость, а компонент обязан быть один файл.
const STYLES = `
:where([data-vibeui-block="radio-006"]){
--vibeui-radio-006-bg:oklch(1 0 0);
--vibeui-radio-006-card:oklch(0.99 0.002 265);
--vibeui-radio-006-fg:oklch(0.22 0.014 265);
--vibeui-radio-006-muted:oklch(0.55 0.014 265);
--vibeui-radio-006-border:oklch(0.9 0.006 265);
--vibeui-radio-006-accent:oklch(0.55 0.19 30);
--vibeui-radio-006-tint:oklch(0.55 0.19 30 / 8%);
--vibeui-radio-006-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="radio-006"]{
display:flex;flex-direction:column;
width:100%;max-width:22rem;box-sizing:border-box;
margin:0;padding:0.5rem 0.875rem 0.875rem;
background:var(--vibeui-radio-006-bg);
border:1px solid var(--vibeui-radio-006-border);border-radius:0.875rem;
font-family:var(--vibeui-radio-006-font);color:var(--vibeui-radio-006-fg);
}
[data-vibeui-block="radio-006"] legend{
float:left;width:100%;padding:0;margin:0.375rem 0 0.5rem;
font-size:0.8125rem;font-weight:650;
}
[data-vibeui-block="radio-006"] [data-part="grid"]{
clear:both;display:grid;gap:0.5rem;
grid-template-columns:repeat(auto-fit,minmax(5rem,1fr));
}
[data-vibeui-block="radio-006"] [data-part="tile"]{
display:flex;flex-direction:column;align-items:center;gap:0.375rem;
padding:0.75rem 0.375rem;border-radius:0.75rem;cursor:pointer;text-align:center;
border:1.5px solid var(--vibeui-radio-006-border);
background:var(--vibeui-radio-006-card);
color:var(--vibeui-radio-006-muted);
transition:border-color .16s ease,background-color .16s ease,color .16s ease;
}
[data-vibeui-block="radio-006"] [data-part="tile"]:hover{border-color:var(--vibeui-radio-006-muted)}
[data-vibeui-block="radio-006"] [data-part="tile"]:has(input:checked){
border-color:var(--vibeui-radio-006-accent);
background:var(--vibeui-radio-006-tint);
color:var(--vibeui-radio-006-accent);
}
[data-vibeui-block="radio-006"] [data-part="tile"]:has(input:focus-visible){
outline:2px solid var(--vibeui-radio-006-accent);outline-offset:2px;
}
[data-vibeui-block="radio-006"] input{
position:absolute;width:1px;height:1px;margin:0;
clip-path:inset(50%);overflow:hidden;white-space:nowrap;
}
/* Иконка наследует цвет плитки: одно правило красит и рисунок, и подпись. */
[data-vibeui-block="radio-006"] svg{width:1.5rem;height:1.5rem;display:block}
[data-vibeui-block="radio-006"] [data-part="name"]{
font-size:0.75rem;font-weight:600;line-height:1.2;color:var(--vibeui-radio-006-fg);
}
[data-vibeui-block="radio-006"] [data-part="tile"]:has(input:checked) [data-part="name"]{color:var(--vibeui-radio-006-accent)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="radio-006"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_OPTIONS: Radio006Option[] = [
  {
    value: "phone",
    label: "Телефон",
    path: "M7 2h10a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm3 17h4",
  },
  {
    value: "tablet",
    label: "Планшет",
    path: "M5 2h14a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm6 17h2",
  },
  {
    value: "laptop",
    label: "Ноутбук",
    path: "M4 5h16v11H4zM2 19h20",
  },
  {
    value: "tv",
    label: "Телевизор",
    path: "M3 6h18v12H3zM8 21h8M12 18v3",
  },
]

/**
 * Радиогруппа плитками с иконками: inline-SVG на currentColor, без пакетов.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Radio006({
  legend = "Основное устройство",
  options = DEFAULT_OPTIONS,
  name = "vibeui-radio-006",
  defaultValue = "laptop",
  accent,
  className,
  style,
  ...props
}: Radio006Props) {
  const palette = {
    ...(accent ? { "--vibeui-radio-006-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-radio-006" precedence="medium">
        {STYLES}
      </style>
      <fieldset
        {...props}
        data-vibeui-block="radio-006"
        className={className}
        style={palette}
      >
        <legend>{legend}</legend>
        <div data-part="grid">
          {options.map((option) => (
            <label key={option.value} data-part="tile">
              <input
                type="radio"
                name={name}
                value={option.value}
                defaultChecked={option.value === defaultValue}
              />
              <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                <path
                  d={option.path}
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="1.6"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
              <span data-part="name">{option.label}</span>
            </label>
          ))}
        </div>
      </fieldset>
    </>
  )
}