انتقل إلى المحتوى
uiify

Radio group

مجموعة مدخلات radio أصلية ينتقل بينها التركيز بلوحة المفاتيح.

المتصفحات
Chrome 123+Firefox 121+Safari 17.5+
الفئة
0
الحجم المضغوط
946 B
ميزانية التخطيط القسري
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", className: "" },
        { value: "pro", label: "Pro", 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] {
    --uiify-checkbox-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.6 8.6l3 3L12.4 4.8' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
      center / 100% 100% no-repeat;

    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);
    corner-shape: squircle;
  }

  [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);
    mask: var(--uiify-checkbox-mask);
  }

  [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);
    mask: none;
    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 [&>[data-part=item]]:flex
        [&>[data-part=item]]:items-center [&>[data-part=item]]:gap-sm [&_[data-part=item]>label]:flex-auto
        [&_[data-part=item]>label]:cursor-pointer [&_[data-part=item]>label]:select-none
      "
      id="plan"
      aria-label="Billing plan"
      defaultValue="pro"
      items={[
        {
          value: "free",
          label: "Free",
          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)_16%,_transparent)] " +
            "disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 " +
            "data-[disabled]:cursor-not-allowed",
        },
        {
          value: "pro",
          label: "Pro",
          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)_16%,_transparent)] " +
            "disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 " +
            "data-[disabled]:cursor-not-allowed",
        },
      ]}
    />
  );
}
/* app.css */

@layer reset, ui, app, tokens, base, demo, components, utilities, theme;

@import "@gunkin/uiify/styles/reset";

@import "@gunkin/uiify/components/behavior.css";

@import "@gunkin/uiify/styles/tokens";

@import "@gunkin/uiify/styles/tailwind";

@import "tailwindcss/utilities.css";

@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }

@source inline('flex flex-col gap-md p-md data-[orientation=horizontal]:flex-row [&>[data-part=item]]:flex [&>[data-part=item]]:items-center [&>[data-part=item]]:gap-sm [&_[data-part=item]>label]:flex-auto [&_[data-part=item]>label]:cursor-pointer [&_[data-part=item]>label]:select-none 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)_16%,_transparent)] disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed');

@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }

/* Authored animation and registered properties used by the Tailwind skin.
 * Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
  @keyframes uiify-presence-in {
    from {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@layer components {
  @keyframes uiify-presence-out {
    to {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@property --uiify-spinner-tail {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --uiify-spinner-length {
  syntax: "<angle>";
  inherits: false;
  initial-value: 36deg;
}

@layer components {
  @keyframes uiify-spinner-rotate {
    to {
      transform: rotate(360deg);
    }
  }
}

@layer components {
  @keyframes uiify-spinner-orbit {
    0% {
      --uiify-spinner-tail: 0deg;
      --uiify-spinner-length: 36deg;
    }

    50% {
      --uiify-spinner-tail: 90deg;
      --uiify-spinner-length: 324deg;
    }

    100% {
      --uiify-spinner-tail: 360deg;
      --uiify-spinner-length: 36deg;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-pulse {
    50% {
      opacity: 0.45;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-shimmer {
    to {
      background-position: -200% 0;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-reveal {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

@property --candle-body-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-body-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-slot {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 100%;
}

@property --candle-wick-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-wick-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-x {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@property --line-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-prev-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-count {
  syntax: "<integer>";
  inherits: false;
  initial-value: 1;
}

@property --line-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@layer components {
  @keyframes uiify-otp-input-caret-blink {
    50% {
      opacity: 0;
    }
  }
}

API

RadioGroup

الخاصيةالنوعافتراضيالوصف
id*string—بادئة المعرّف للمجموعة ولكل مدخل radio مولّد.
items*RadioGroupItem[]—الخيارات المطلوب عرضها؛ تعتمد المجموعة على البيانات وليست تركيبًا من أجزاء.
defaultValuestring—القيمة المؤشّر عليها عند أول عرض؛ يدير المتصفح التحديد بعدها، لذا تعمل المجموعة دون JavaScript.
namestring—اسم حقل النموذج المشترك بين جميع مدخلات radio في المجموعة.
disabledbooleanfalseيعطّل كل مدخل radio في المجموعة.
orientation"horizontal" | "vertical""vertical"اتجاه التخطيط الذي يُبلّغ للتقنيات المساعدة ويُستخدم للتنسيق.
classNamestring—الفئة المطبّقة على عنصر المجموعة.
aria-labelstring—اسم مجموعة أزرار الاختيار للتقنيات المساعدة.

RadioGroupItem

الخاصيةالنوعافتراضيالوصف
value*string—القيمة المرسلة عند تحديد زر الاختيار هذا.
label*ReactNode—محتوى التسمية المعروض بجانب زر الاختيار هذا.
disabledboolean—يعطّل زر الاختيار هذا فقط.
classNamestring—الفئة المطبّقة على غلاف هذا العنصر.

لوحة المفاتيح

المفتاحالإجراء
Tabنقل التركيز إلى المجموعة
Arrow keysالتنقل والتحديد بين الخيارات

إمكانية الوصول

مدخلات radio أصلية في مجموعة ذات تركيز متنقّل.

نقاط التنسيق

data-statedata-disabled