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

Toggle group

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

المتصفحات
Chrome 123+Firefox 121+Safari 17.5+
الفئة
0
الحجم المضغوط
1.00 kB
ميزانية التخطيط القسري
0 ms
ميزانية axe
0
متوافق مع SSR
نعم
مبني على
elements/choice
العقد المنشور
بيئة الاختبار

المختبر

Toggle group

Single

Multiple

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

export default function Example() {
  return (
    <ToggleGroup
      aria-label="Text alignment"
      className=""
      id="alignment"
      type="single"
      defaultValue="left"
      items={[
        { value: "left", label: "Left" },
        { value: "center", label: "Center" },
        { value: "right", label: "Right" },
      ]}
    />
  );
}
/* Toggle + native-control toggle groups. The labels are styled by the Button skin
   (`data-uiify-button`); this file only places the native controls behind them. */

@layer components {
  [data-uiify-toggle] {
    position: relative;
    display: inline-flex;
  }

  [data-uiify-toggle] > input {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
  }

  [data-uiify-toggle] > input:disabled {
    cursor: not-allowed;
  }

  [data-uiify-toggle] > input:focus-visible + [data-uiify-button],
  [data-uiify-toggle-group] [data-part="item"] > input:focus-visible + [data-uiify-button] {
    box-shadow: 0 0 0 var(--ring-width) color-mix(in oklch, var(--ring) 16%, transparent);
  }

  [data-uiify-toggle-group] {
    display: inline-flex;
    gap: var(--space-xs);
  }

  [data-uiify-toggle-group][data-orientation="vertical"] {
    flex-direction: column;
  }

  [data-uiify-toggle-group] [data-part="item"] {
    position: relative;
  }

  [data-uiify-toggle-group] [data-part="item"] > input {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    overflow: hidden;
    /* stylelint-disable-next-line property-no-deprecated -- fallback for older browsers */
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Group items are tighter than a standalone button, so a one-glyph item stays square. */
  [data-uiify-toggle-group] [data-part="item"] > label {
    padding-inline: 0.75rem;
  }
}
import { ToggleGroup } from "@gunkin/uiify";

export default function Example() {
  return (
    <ToggleGroup
      aria-label="Text alignment"
      className={
        "[&_[data-part=item]>input:focus-visible+[data-uiify-button]]:[box-shadow:0_0_0_var(--ring-width)_color-mix(" +
        "in_oklch,_var(--ring)_16%,_transparent)] inline-flex gap-xs data-[orientation=vertical]:flex-col [&_[data-p" +
        "art=item]]:relative [&_[data-part=item]>input]:absolute [&_[data-part=item]>input]:[inline-size:1px] [&_[da" +
        "ta-part=item]>input]:[block-size:1px] [&_[data-part=item]>input]:[margin:-1px] [&_[data-part=item]>input]:o" +
        "verflow-hidden [&_[data-part=item]>input]:[clip:rect(0_0_0_0)] [&_[data-part=item]>input]:[clip-path:inset(" +
        "50%)] [&_[data-part=item]>input]:whitespace-nowrap [&_[data-part=item]>label]:relative [&_[data-part=item]>" +
        "label]:isolate [&_[data-part=item]>label]:inline-flex [&_[data-part=item]>label]:min-h-8 [&_[data-part=item" +
        "]>label]:min-w-8 [&_[data-part=item]>label]:items-center [&_[data-part=item]>label]:justify-center [&_[data" +
        "-part=item]>label]:gap-1 [&_[data-part=item]>label]:rounded-full [&_[data-part=item]>label]:border-[length:" +
        "var(--border-width)] [&_[data-part=item]>label]:border-solid [&_[data-part=item]>label]:border-transparent " +
        "[&_[data-part=item]>label]:bg-transparent [&_[data-part=item]>label]:text-foreground [&_[data-part=item]>la" +
        "bel]:bg-clip-padding [&_[data-part=item]>label]:px-3 [&_[data-part=item]>label]:py-0 [&_[data-part=item]>la" +
        "bel]:text-foreground [&_[data-part=item]>label]:text-sm [&_[data-part=item]>label]:font-medium [&_[data-par" +
        "t=item]>label]:leading-5 [&_[data-part=item]>label]:no-underline [&_[data-part=item]>label]:cursor-pointer " +
        "[&_[data-part=item]>label]:[transition:var(--transition-colors)] [&_[data-part=item]>input:checked+label]:b" +
        "order-transparent [&_[data-part=item]>input:checked+label]:bg-primary [&_[data-part=item]>input:checked+lab" +
        "el]:text-primary-foreground [&_[data-part=item]>label[data-variant=outline]]:[--uiify-button-rim:color-mix(" +
        "in_oklch,_var(--foreground)_25%,_transparent)] [&_[data-part=item]>input:checked+label[data-variant=outline" +
        "]]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&_[data-part=item]>label[data-variant=outline]]:before:cont" +
        "ent-none [&_[data-part=item]>label[data-variant=outline]]:after:content-[''] [&_[data-part=item]>label[data" +
        "-variant=outline]]:after:absolute [&_[data-part=item]>label[data-variant=outline]]:after:z-[-1] [&_[data-pa" +
        "rt=item]>label[data-variant=outline]]:after:[inset:calc(-1_*_var(--border-width))] [&_[data-part=item]>labe" +
        "l[data-variant=outline]]:after:rounded-[inherit] [&_[data-part=item]>label[data-variant=outline]]:after:poi" +
        "nter-events-none [&_[data-part=item]>label[data-variant=outline]]:after:[padding:var(--border-width)] [&_[d" +
        "ata-part=item]>label[data-variant=outline]]:after:[background-image:linear-gradient(to_bottom,_var(--uiify-" +
        "button-rim),_transparent_75%)] [&_[data-part=item]>label[data-variant=outline]]:after:[mask:linear-gradient" +
        "(#fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [@media_(any-hover:_hover)_and_(any-pointer:_fin" +
        "e)]:[&_[data-part=item]>input:not(:disabled)+label[data-variant=outline]:hover]:[background-color:color-mix" +
        "(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&_[dat" +
        "a-part=item]>input:checked:not(:disabled)+label[data-variant=outline]:hover]:[background-color:color-mix(in" +
        "_oklch,_var(--primary)_88%,_var(--primary-foreground))] [&_[data-part=item]>input:where(:not(:disabled))+la" +
        "bel[data-variant=outline]:active]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)" +
        "] [&_[data-part=item]>label:not([data-size])]:min-h-11 [&_[data-part=item]>label:not([data-size])]:min-w-11" +
        " [&_[data-part=item]>label:not([data-size])]:text-base [&_[data-part=item]>label:not([data-size])]:leading-" +
        "6 [&_[data-part=item]>input:not(:checked)+label]:[border-color:color-mix(in_oklch,_var(--foreground)_20%,_t" +
        "ransparent)]"
      }
      id="alignment"
      type="single"
      defaultValue="left"
      items={[
        { value: "left", label: "Left" },
        { value: "center", label: "Center" },
        { value: "right", label: "Right" },
      ]}
    />
  );
}
/* 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('[&_[data-part=item]>input:focus-visible+[data-uiify-button]]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] inline-flex gap-xs data-[orientation=vertical]:flex-col [&_[data-part=item]]:relative [&_[data-part=item]>input]:absolute [&_[data-part=item]>input]:[inline-size:1px] [&_[data-part=item]>input]:[block-size:1px] [&_[data-part=item]>input]:[margin:-1px] [&_[data-part=item]>input]:overflow-hidden [&_[data-part=item]>input]:[clip:rect(0_0_0_0)] [&_[data-part=item]>input]:[clip-path:inset(50%)] [&_[data-part=item]>input]:whitespace-nowrap [&_[data-part=item]>label]:relative [&_[data-part=item]>label]:isolate [&_[data-part=item]>label]:inline-flex [&_[data-part=item]>label]:min-h-8 [&_[data-part=item]>label]:min-w-8 [&_[data-part=item]>label]:items-center [&_[data-part=item]>label]:justify-center [&_[data-part=item]>label]:gap-1 [&_[data-part=item]>label]:rounded-full [&_[data-part=item]>label]:border-[length:var(--border-width)] [&_[data-part=item]>label]:border-solid [&_[data-part=item]>label]:border-transparent [&_[data-part=item]>label]:bg-transparent [&_[data-part=item]>label]:text-foreground [&_[data-part=item]>label]:bg-clip-padding [&_[data-part=item]>label]:px-3 [&_[data-part=item]>label]:py-0 [&_[data-part=item]>label]:text-sm [&_[data-part=item]>label]:font-medium [&_[data-part=item]>label]:leading-5 [&_[data-part=item]>label]:no-underline [&_[data-part=item]>label]:cursor-pointer [&_[data-part=item]>label]:[transition:var(--transition-colors)] [&_[data-part=item]>input:checked+label]:border-transparent [&_[data-part=item]>input:checked+label]:bg-primary [&_[data-part=item]>input:checked+label]:text-primary-foreground [&_[data-part=item]>label[data-variant=outline]]:[--uiify-button-rim:color-mix(in_oklch,_var(--foreground)_25%,_transparent)] [&_[data-part=item]>input:checked+label[data-variant=outline]]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&_[data-part=item]>label[data-variant=outline]]:before:content-none [&_[data-part=item]>label[data-variant=outline]]:after:absolute [&_[data-part=item]>label[data-variant=outline]]:after:z-[-1] [&_[data-part=item]>label[data-variant=outline]]:after:[inset:calc(-1_*_var(--border-width))] [&_[data-part=item]>label[data-variant=outline]]:after:rounded-[inherit] [&_[data-part=item]>label[data-variant=outline]]:after:pointer-events-none [&_[data-part=item]>label[data-variant=outline]]:after:[padding:var(--border-width)] [&_[data-part=item]>label[data-variant=outline]]:after:[background-image:linear-gradient(to_bottom,_var(--uiify-button-rim),_transparent_75%)] [&_[data-part=item]>label[data-variant=outline]]:after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&_[data-part=item]>input:not(:disabled)+label[data-variant=outline]:hover]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&_[data-part=item]>input:checked:not(:disabled)+label[data-variant=outline]:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [&_[data-part=item]>input:where(:not(:disabled))+label[data-variant=outline]:active]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] [&_[data-part=item]>label:not([data-size])]:min-h-11 [&_[data-part=item]>label:not([data-size])]:min-w-11 [&_[data-part=item]>label:not([data-size])]:text-base [&_[data-part=item]>label:not([data-size])]:leading-6 [&_[data-part=item]>input:not(:checked)+label]:[border-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)]');

@source inline("[&_[data-part=item]>label[data-variant=outline]]:after:content-['']");

@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

ToggleGroup

الخاصيةالنوعافتراضيالوصف
id*string—أساس المعرّفات الحتمية والاسم المشترك لمجموعة المدخلات.
type"single" | "multiple""single"يحدّد إمكان ضغط زر تبديل واحد أو عدة أزرار، باختيار مدخلات radio أو checkbox.
items*readonly ToggleGroupItem[]—صيغة كل عنصر هي { value, label, disabled? }.
defaultValuestring | readonly string[] | undefined—القيمة المضغوطة عند أول عرض. غير متحكّم بها، لذا تستمر بالعمل دون JavaScript.
valuestring | readonly string[]—القيمة المتحكّم بها في ToggleGroupClient؛ يستخدم single نصًا، ويستخدم multiple مصفوفة.
onValueChange(value) => void—دالة رد نداء التغيير في ToggleGroupClient؛ استخدم ToggleGroupClient لحالة يديرها العميل.
disabledbooleanfalseيعطّل كل عنصر ويضع data-disabled على المجموعة.
aria-label / aria-labelledbystring—اسم المجموعة للتقنيات المساعدة؛ حدّده إن لم توجد تسمية مرئية للمجموعة.
orientation"horizontal" | "vertical""horizontal"الاتجاه الذي تُرتّب فيه أزرار التبديل.
classNamestring—الفئة المطبّقة على جذر المجموعة.

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

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

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

يستخدم radiogroup أصلية ذات تسمية للتحديد المفرد، أو group للتحديد المتعدد. استخدم ToggleGroupClient لسلوك value/onValueChange المتحكّم به.

نقاط التنسيق

data-statedata-disabled