К содержимому
uiify

Switch

Элемент управления, который включает или выключает одну настройку.

Браузеры
Chrome 123+Firefox 121+Safari 17.5+
Уровень
0
Сжатый размер
319 B
Лимит принудительной компоновки
0 ms
Лимит axe
0
Совместим с SSR
Да
Публичный контракт
Тестовый стенд

Песочница

Switch

По умолчанию

Отключена

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

export default function Example() {
  return <Switch className="" aria-label="Email notifications" defaultChecked />;
}
/* 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 { Switch } from "@gunkin/uiify";

export default function Example() {
  return (
    <Switch
      className="
        focus-visible:[outline:none]
        focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)]
        appearance-none relative [inline-size:2.25rem] [block-size:1.25rem] m-0 flex-none border-0 border-current
        border-none rounded-[var(--radius-full)] bg-input cursor-pointer
        [transition:background-color_var(--duration-fast)_var(--ease-out)] before:content-[''] before:absolute
        before:[inset-block-start:2px] before:[inset-inline-start:2px] before:[inline-size:calc(1.25rem_-_4px)]
        before:[block-size:calc(1.25rem_-_4px)] before:rounded-[var(--radius-full)]
        before:[background-color:var(--gray-0)] before:[box-shadow:var(--shadow-1)]
        before:[transition:translate_var(--duration-fast)_var(--ease-out)] checked:bg-primary
        data-[state=checked]:bg-primary checked:before:[translate:1rem_0] data-[state=checked]:before:[translate:1rem_0]
        disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed
      "
      aria-label="Email notifications"
      defaultChecked
    />
  );
}
/* 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('focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] appearance-none relative [inline-size:2.25rem] [block-size:1.25rem] m-0 flex-none border-0 border-current border-none rounded-[var(--radius-full)] bg-input cursor-pointer [transition:background-color_var(--duration-fast)_var(--ease-out)] before:absolute before:[inset-block-start:2px] before:[inset-inline-start:2px] before:[inline-size:calc(1.25rem_-_4px)] before:[block-size:calc(1.25rem_-_4px)] before:rounded-[var(--radius-full)] before:[background-color:var(--gray-0)] before:[box-shadow:var(--shadow-1)] before:[transition:translate_var(--duration-fast)_var(--ease-out)] checked:bg-primary data-[state=checked]:bg-primary checked:before:[translate:1rem_0] data-[state=checked]:before:[translate:1rem_0] disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed');

@source inline("before: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

Switch

СвойствоТипПо умолчаниюОписание
checkedboolean—Состояние отметки, делающее выключатель управляемым.
defaultCheckedboolean—Начальное состояние отметки неуправляемого выключателя; далее им владеет нативный checkbox.
onCheckedChange(checked: boolean) => void—Вызывается с новым состоянием отметки, когда пользователь переключает выключатель.
disabledbooleanfalseОтключает нативное поле ввода и задаёт атрибут data-disabled.
renderReactElement | ((props, state) => ReactElement)—Заменяет отображаемый элемент, сохраняя поведение и связи доступности.

Клавиатура

КлавишаДействие
SpaceПереключить при наличии фокуса

Доступность

Роль switch с aria-checked; управление с клавиатуры соответствует нативному флажку.

Привязка стилей

data-statedata-disabled