דילוג לתוכן
uiify

Radio group

קבוצת כפתורי רדיו מובנים עם מיקוד מקלדת נודד.

דפדפנים
Chrome 123+Firefox 121+Safari 17.5+
שכבה
0
גודל חבילה דחוסה
1.34 kB
מגבלת פריסה כפויה
0 ms
מגבלת Axe
0
תואם SSR
כן
מבוסס על
elements/choice
חוזה מפורסם
סביבת בדיקות

מגרש ניסויים

Radio group

ברירת מחדל

import { RadioGroup } from "@gunkin/uiify";

export default function Example() {
  return (
    <RadioGroup
      className=""
      id="plan"
      aria-label="Billing plan"
      defaultValue="pro"
      items={[
        { value: "free", label: "Free" },
        { value: "pro", label: "Pro" },
      ]}
      slotProps={{
        item: { className: "" },
        input: { className: "" },
        label: { className: "" },
      }}
    />
  );
}
/* Checkbox, Radio, Switch — all native <input>s styled with appearance:none.
   State comes from :checked / :indeterminate (works uncontrolled) and the
   [data-state] / [data-disabled] attributes (set when controlled). Thumbs and
   checkmarks are pseudo-elements on the input itself. */

@layer components {
  :where([data-uiify-checkbox] > input, [data-uiify-radio]) {
    appearance: none;
    inline-size: 1.25rem;
    block-size: 1.25rem;
    margin: 0;
    border: var(--border-width) solid var(--input);
    background-color: var(--card);
    display: inline-grid;
    place-content: center;
    cursor: pointer;
    transition: var(--transition-colors);
  }

  [data-uiify-checkbox] {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    cursor: pointer;
    user-select: none;
    color: var(--foreground);
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
  }

  [data-uiify-checkbox] > input {
    border-radius: var(--radius-xs);
  }

  [data-uiify-radio] {
    border-radius: var(--radius-full);
  }

  /* Checked fill (covers controlled [data-state] and uncontrolled :checked). */
  [data-uiify-checkbox] > input:checked,
  [data-uiify-checkbox] > input[data-state="checked"] {
    border-color: var(--primary);
    background-color: var(--primary);
  }

  /* Radio checked state is a thick accent ring around a white center, not a
     filled dot — matches the 2a/2b handoff's target/bullseye radio anatomy. */
  [data-uiify-radio]:checked,
  [data-uiify-radio][data-state="checked"] {
    border-width: 5px;
    border-color: var(--primary);
    background-color: var(--card);
  }

  /* Checkmark. */
  [data-uiify-checkbox] > input::before {
    content: "";
    inline-size: 1rem;
    block-size: 1rem;
    transform: scale(0);
    transition: transform var(--duration-fast) var(--ease-out);
    background-color: var(--primary-foreground);
    clip-path: polygon(28.4% 46.4%, 20% 59%, 42.8% 80%, 80% 33.2%, 70.4% 23.6%, 42.8% 60.8%);
  }

  [data-uiify-checkbox] > input:checked::before,
  [data-uiify-checkbox] > input[data-state="checked"]::before {
    transform: scale(1);
  }

  /* Indeterminate dash. */
  [data-uiify-checkbox] > input:indeterminate,
  [data-uiify-checkbox] > input[data-state="indeterminate"] {
    border-color: var(--primary);
    background-color: var(--primary);
  }

  [data-uiify-checkbox] > input:indeterminate::before,
  [data-uiify-checkbox] > input[data-state="indeterminate"]::before {
    transform: scale(1);
    clip-path: inset(42% 12% 42% 12%);
  }

  :where([data-uiify-checkbox] > input, [data-uiify-radio]):disabled,
  :where([data-uiify-checkbox] > input, [data-uiify-radio])[data-disabled] {
    opacity: 0.5;
    cursor: not-allowed;
  }

  [data-uiify-checkbox]:has(> input:disabled) {
    opacity: 0.5;
    cursor: not-allowed;
  }

  [data-uiify-radio-group] {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
  }

  /* Switch — a pill track with a sliding thumb pseudo-element. */
  [data-uiify-switch] {
    appearance: none;
    position: relative;
    inline-size: 2.25rem;
    block-size: 1.25rem;
    margin: 0;
    flex: none;
    border: none;
    border-radius: var(--radius-full);
    background-color: var(--input);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out);
  }

  [data-uiify-switch]::before {
    content: "";
    position: absolute;
    inset-block-start: 2px;
    inset-inline-start: 2px;
    inline-size: calc(1.25rem - 4px);
    block-size: calc(1.25rem - 4px);
    border-radius: var(--radius-full);
    background-color: var(--gray-0);
    box-shadow: var(--shadow-1);
    transition: translate var(--duration-fast) var(--ease-out);
  }

  [data-uiify-switch]:checked,
  [data-uiify-switch][data-state="checked"] {
    background-color: var(--primary);
  }

  [data-uiify-switch]:checked::before,
  [data-uiify-switch][data-state="checked"]::before {
    translate: 1rem 0;
  }

  [data-uiify-switch]:disabled,
  [data-uiify-switch][data-disabled] {
    opacity: 0.5;
    cursor: not-allowed;
  }
}

@layer components {
  label:has([data-uiify-radio]),
  [data-uiify-radio-group] label {
    user-select: none;
    cursor: pointer;
  }
}
import { RadioGroup } from "@gunkin/uiify";

export default function Example() {
  return (
    <RadioGroup
      className="flex flex-col gap-md p-md data-[orientation=horizontal]:flex-row"
      id="plan"
      aria-label="Billing plan"
      defaultValue="pro"
      items={[
        { value: "free", label: "Free" },
        { value: "pro", label: "Pro" },
      ]}
      slotProps={{
        item: { className: "flex items-center gap-sm" },
        input: {
          className:
            "appearance-none inline-grid size-5 flex-none place-content-center m-0 " +
            "border-[length:var(--border-width)] border-solid border-input rounded-full " +
            "bg-card cursor-pointer [transition:var(--transition-colors)] " +
            "checked:border-[5px] checked:border-primary data-[state=checked]:border-[5px] " +
            "data-[state=checked]:border-primary focus-visible:outline-none " +
            "focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_20%,_transparent)] " +
            "disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 " +
            "data-[disabled]:cursor-not-allowed",
        },
        label: { className: "flex-auto cursor-pointer select-none" },
      }}
    />
  );
}

API

RadioGroup

מאפייןטיפוסברירת מחדלתיאור
id*string—קידומת מזהה לקבוצה ולכל קלט רדיו שנוצר.
items*RadioGroupItem[]—האפשרויות להצגה; הקבוצה מונעת מנתונים ולא מורכבת מחלקים.
defaultValuestring—הערך המסומן בהצגה הראשונה; מכאן והלאה הדפדפן מנהל את הבחירה, ולכן הקבוצה פועלת ללא JavaScript.
namestring—שם שדה הטופס המשותף לכל קלטי הרדיו בקבוצה.
disabledbooleanfalseמשבית את כל קלטי הרדיו בקבוצה.
orientation"horizontal" | "vertical""vertical"כיוון הפריסה המדווח לטכנולוגיות מסייעות ומשמש לעיצוב.
classNamestring—המחלקה המוחלת על אלמנט הקבוצה.
slotPropsRadioGroupSlotProps—מאפיינים מובנים לאלמנטי הפריט, הקלט והתווית שנוצרים; ניתן למקד משבצות חוזרות בפריט לפי ערכו.
aria-labelstring—שם נגיש לקבוצת הרדיו.

RadioGroupItem

מאפייןטיפוסברירת מחדלתיאור
value*string—הערך הנשלח כשקלט רדיו זה נבחר.
label*ReactNode—תוכן התווית המוצג לצד קלט רדיו זה.
disabledboolean—משבית רק את קלט הרדיו הזה.
classNamestring—המחלקה המוחלת על קלט הרדיו המובנה של פריט זה.

מקלדת

מקשפעולה
Tabהעברת המיקוד לקבוצה
Arrow keysמעבר ובחירה בין אפשרויות

נגישות

כפתורי רדיו מובנים בקבוצה עם מיקוד נודד.

נקודות חיבור לעיצוב

data-statedata-disabled
דף זה תורגם אוטומטית וטרם נבדק.