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

OTP input

مدخل لرموز الاستخدام الواحد، بخانة لكل محرف.

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

المختبر

OTP Input

Default

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

export default function Example() {
  return (
    <OtpInput.Root className="" id="verification-code" length={6}>
      {Array.from({ length: 6 }, (_, index) => (
        <OtpInput.Slot className="" index={index} key={index} />
      ))}
      <OtpInput.HiddenInput />
    </OtpInput.Root>
  );
}
/* OTP input — segmented code entry slots. */

@layer components {
  [data-uiify-otp-input] {
    position: relative;
    display: inline-flex;
    gap: var(--space-sm);
    cursor: text;
  }

  [data-uiify-otp-input] [data-part="slot"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border: var(--border-width) solid var(--input);
    border-radius: var(--radius-md);
    corner-shape: squircle;
    background-color: var(--card);
    color: var(--foreground);
    font-size: var(--text-lg);
    font-weight: var(--weight-medium);
    transition: var(--transition-colors);
  }

  [data-uiify-otp-input] [data-part="slot"][data-active] {
    border-color: var(--primary);
    outline: 2px solid color-mix(in oklch, var(--ring) 35%, transparent);
    outline-offset: 0;
  }

  [data-uiify-otp-input] [data-part="slot"][data-active]::after {
    inline-size: 1px;
    block-size: 1em;
    background-color: currentcolor;
    content: "";
    animation: uiify-otp-input-caret-blink 1s step-end infinite;
  }

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

  @media (prefers-reduced-motion: reduce) {
    [data-uiify-otp-input] [data-part="slot"][data-active]::after {
      animation: none;
    }
  }

  [data-uiify-otp-input] [data-part="slot"][data-filled] {
    border-color: var(--input);
  }

  [data-uiify-otp-input][data-disabled] [data-part="slot"] {
    opacity: 0.5;
    cursor: not-allowed;
  }
}

@layer components {
  [data-uiify-otp-input]:not([data-disabled]) {
    cursor: text;
  }
}
import { OtpInput } from "@gunkin/uiify";

export default function Example() {
  return (
    <OtpInput.Root
      className="
        relative inline-flex gap-sm [cursor:text] [&_[data-part=slot]]:inline-flex [&_[data-part=slot]]:items-center
        [&_[data-part=slot]]:justify-center [&_[data-part=slot]]:[inline-size:2.5rem]
        [&_[data-part=slot]]:[block-size:2.5rem] [&_[data-part=slot]]:border-[length:var(--border-width)]
        [&_[data-part=slot]]:border-solid [&_[data-part=slot]]:border-input [&_[data-part=slot]]:rounded-md
        [&_[data-part=slot]]:[corner-shape:squircle] [&_[data-part=slot]]:bg-card [&_[data-part=slot]]:text-foreground
        [&_[data-part=slot]]:text-lg [&_[data-part=slot]]:font-[number:var(--weight-medium)]
        [&_[data-part=slot]]:[transition:var(--transition-colors)] [&_[data-part=slot][data-active]]:border-primary
        [&_[data-part=slot][data-active]]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)]
        [&_[data-part=slot][data-active]]:[outline-offset:0] [&_[data-part=slot][data-active]::after]:[inline-size:1px]
        [&_[data-part=slot][data-active]::after]:[block-size:1em] [&_[data-part=slot][data-active]::after]:bg-current
        [&_[data-part=slot][data-active]::after]:content-['']
        [&_[data-part=slot][data-active]::after]:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite]
        motion-reduce:[&_[data-part=slot][data-active]::after]:[animation:none]
        [&_[data-part=slot][data-filled]]:border-input [&[data-disabled]_[data-part=slot]]:opacity-50
        [&[data-disabled]_[data-part=slot]]:cursor-not-allowed [&:not([data-disabled])]:[cursor:text]
      "
      id="verification-code"
      length={6}
    >
      {Array.from({ length: 6 }, (_, index) => (
        <OtpInput.Slot
          className="
            grid place-items-center size-10 border-[length:var(--border-width)] border-solid border-input rounded-md
            bg-card text-foreground text-lg font-medium tabular-nums [transition:var(--transition-colors)]
            data-[active]:border-primary
            data-[active]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)]
            data-[active]:[outline-offset:0] data-[active]:after:content-[''] data-[active]:after:[inline-size:1px]
            data-[active]:after:[block-size:1em] data-[active]:after:bg-current
            data-[active]:after:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite]
            motion-reduce:data-[active]:after:[animation:none] data-[filled]:border-input data-[disabled]:opacity-50
            data-[disabled]:cursor-not-allowed
          "
          index={index}
          key={index}
        />
      ))}
      <OtpInput.HiddenInput />
    </OtpInput.Root>
  );
}
/* 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('relative inline-flex gap-sm [cursor:text] [&_[data-part=slot]]:inline-flex [&_[data-part=slot]]:items-center [&_[data-part=slot]]:justify-center [&_[data-part=slot]]:[inline-size:2.5rem] [&_[data-part=slot]]:[block-size:2.5rem] [&_[data-part=slot]]:border-[length:var(--border-width)] [&_[data-part=slot]]:border-solid [&_[data-part=slot]]:border-input [&_[data-part=slot]]:rounded-md [&_[data-part=slot]]:[corner-shape:squircle] [&_[data-part=slot]]:bg-card [&_[data-part=slot]]:text-foreground [&_[data-part=slot]]:text-lg [&_[data-part=slot]]:font-[number:var(--weight-medium)] [&_[data-part=slot]]:[transition:var(--transition-colors)] [&_[data-part=slot][data-active]]:border-primary [&_[data-part=slot][data-active]]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)] [&_[data-part=slot][data-active]]:[outline-offset:0] [&_[data-part=slot][data-active]::after]:[inline-size:1px] [&_[data-part=slot][data-active]::after]:[block-size:1em] [&_[data-part=slot][data-active]::after]:bg-current [&_[data-part=slot][data-active]::after]:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite] motion-reduce:[&_[data-part=slot][data-active]::after]:[animation:none] [&_[data-part=slot][data-filled]]:border-input [&[data-disabled]_[data-part=slot]]:opacity-50 [&[data-disabled]_[data-part=slot]]:cursor-not-allowed [&:not([data-disabled])]:[cursor:text] grid place-items-center size-10 border-[length:var(--border-width)] border-solid border-input rounded-md bg-card text-foreground text-lg font-medium tabular-nums [transition:var(--transition-colors)] data-[active]:border-primary data-[active]:[outline:2px_solid_color-mix(in_oklch,_var(--ring)_35%,_transparent)] data-[active]:[outline-offset:0] data-[active]:after:[inline-size:1px] data-[active]:after:[block-size:1em] data-[active]:after:bg-current data-[active]:after:[animation:uiify-otp-input-caret-blink_1s_step-end_infinite] motion-reduce:data-[active]:after:[animation:none] data-[filled]:border-input data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed');

@source inline("[&_[data-part=slot][data-active]::after]:content-[''] data-[active]: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

OtpInput.Root

الخاصيةالنوعافتراضيالوصف
valuestring—الرمز الحالي، ويجعل الحقل متحكّمًا به.
defaultValuestring""الرمز الأولي للحقل غير المتحكّم به.
onValueChange(value: string) => void—تُستدعى بالرمز كلما تغيّر.
onComplete(value: string) => void—تُستدعى حين يبلغ الرمز الطول المضبوط.
lengthnumber6عدد المحارف التي يتّسع لها الرمز.
type"numeric" | "alphanumeric""numeric"المحارف التي يُحتفظ بها عند الكتابة أو اللصق.
maskbooleanfalseيخفي المحارف المُدخلة في الخانات.
disabledbooleanfalseيعطّل المدخل المخفي ويعيّن سمة data-disabled.
autoFocusbooleanfalseيركّز المدخل المخفي عند تثبيت الحقل.
idstring—المعرّف المطبّق على عنصر الجذر المعروض.
renderReactElement | ((props, state) => ReactElement)—استبدل العنصر المعروض مع الحفاظ على توصيلات السلوك وإمكانية الوصول.

OtpInput.Slot

الخاصيةالنوعافتراضيالوصف
index*number—الموضع من الرمز الذي تعرضه هذه الخانة.
renderReactElement | ((props, state) => ReactElement)—استبدل العنصر المعروض مع الحفاظ على توصيلات السلوك وإمكانية الوصول.

OtpInput.HiddenInput

الخاصيةالنوعافتراضيالوصف
autoFocusboolean—يركّز هذا المدخل عند التثبيت، متجاوزًا إعداد الجذر.
renderReactElement | ((props, state) => ReactElement)—استبدل العنصر المعروض مع الحفاظ على توصيلات السلوك وإمكانية الوصول.

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

المفتاحالإجراء
Tabنقل التركيز إلى عنصر التحكم أو منه
Enter / Spaceالتفعيل عند التركيز (لعناصر التحكم الشبيهة بالأزرار)

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

يستخدم HTML الدلالي متى أمكن. افحص بنية HTML المعروضة في المختبر؛ لا تُضاف ARIA إلا حين لا تكفي العناصر الأصلية.

نقاط التنسيق

data-disabled
تُرجمت هذه الصفحة آليًا ولم تُراجع بعد.