Ir para o conteúdo

Field

Estrutura para rótulo, controle e erro.

Navegadores
Chrome 123+Firefox 121+Safari 17.5+
Nível
0
Tamanho compactado
668 B
Limite de layout forçado
0 ms
Limite do axe
0
Compatível com SSR
Sim
Contrato publicado
Ambiente de teste

Experimentar

Field

Padrão

Your display name

Invalid

Password

Use at least eight characters.

Message

Up to 500 characters.

Desativado

Read only

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

export default function Example() {
  return (
    <Field.Root className="" id="email" required>
      <Field.Label className="" fieldId="email" required>
        Email
      </Field.Label>
      <Field.Control className="" fieldId="email" required as="input" type="email" />
      <Field.Description className="" fieldId="email">
        We will only use this for account notices.
      </Field.Description>
      <Field.Error className="" fieldId="email">
        Enter a valid email address.
      </Field.Error>
    </Field.Root>
  );
}
/* Text fields — Input and Textarea share the same surface. Invalid state keys
   off the [data-invalid] attribute the components emit. Textarea autosize uses
   the native field-sizing (Chrome 123+) when the component sets data-autosize. */

@layer components {
  :where([data-uiify-input], [data-uiify-textarea]) {
    display: block;
    inline-size: 100%;
    max-inline-size: 24rem;
    padding-block: var(--space-sm);
    padding-inline: var(--space-md);
    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(--uiify-control-text);
    line-height: var(--leading-normal);
    transition: var(--transition-colors);
  }

  :where([data-uiify-input], [data-uiify-textarea])::placeholder {
    color: var(--muted-foreground);
  }

  :where([data-uiify-input], [data-uiify-textarea]):focus-visible {
    border-color: var(--primary);
  }

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

  :where([data-uiify-input], [data-uiify-textarea])[data-invalid],
  :where([data-uiify-input], [data-uiify-textarea]):user-invalid {
    border-color: var(--destructive);
  }

  :where([data-uiify-input], [data-uiify-textarea])[data-invalid]:focus-visible {
    outline-color: var(--destructive);
  }

  [data-uiify-textarea] {
    min-block-size: 4lh;
    resize: block;
  }

  [data-uiify-textarea][data-autosize] {
    field-sizing: content;
    resize: none;
  }

  [data-uiify-field] {
    display: grid;
    gap: var(--space-sm);
    inline-size: 100%;
    max-inline-size: 24rem;
  }

  [data-uiify-field] [data-part="label"] {
    color: var(--foreground);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-normal);
  }

  [data-uiify-field] [data-part="label"][data-required]::after {
    content: " *";
    color: var(--destructive);
  }

  [data-uiify-field] [data-part="description"] {
    margin: 0;
    color: var(--muted-foreground);
    font-size: var(--text-xs);
    line-height: var(--leading-normal);
  }

  [data-uiify-field] [data-part="error"] {
    margin: 0;
    color: var(--destructive);
    font-size: var(--text-xs);
    line-height: var(--leading-normal);
  }

  /* Shared presence fade when an error message is mounted. */
  @media (prefers-reduced-motion: no-preference) {
    [data-uiify-field] [data-part="error"] {
      transition: opacity var(--duration-fast) var(--ease-out);
    }

    @starting-style {
      [data-uiify-field] [data-part="error"] {
        opacity: 0;
      }
    }
  }
}
import { Field } from "@gunkin/uiify";

export default function Example() {
  return (
    <Field.Root
      className={
        "grid gap-sm [inline-size:100%] [max-inline-size:24rem] [&_[data-part=label]]:text-foreground [&_[data-part=" +
        "label]]:text-sm [&_[data-part=label]]:font-[number:var(--weight-medium)] [&_[data-part=label]]:[line-height" +
        ":var(--leading-normal)] [&_[data-part=label][data-required]::after]:content-[\"_*\"] [&_[data-part=label][dat" +
        "a-required]::after]:text-destructive [&_[data-part=description]]:m-0 [&_[data-part=description]]:text-muted" +
        "-foreground [&_[data-part=description]]:text-xs [&_[data-part=description]]:[line-height:var(--leading-norm" +
        "al)] [&_[data-part=error]]:m-0 [&_[data-part=error]]:text-destructive [&_[data-part=error]]:text-xs [&_[dat" +
        "a-part=error]]:[line-height:var(--leading-normal)] motion-safe:[&_[data-part=error]]:[transition:opacity_va" +
        "r(--duration-fast)_var(--ease-out)] motion-safe:starting:[&_[data-part=error]]:opacity-0"
      }
      id="email"
      required
    >
      <Field.Label
        className={
          "text-foreground text-sm font-[number:var(--weight-medium)] [line-height:var(--leading-normal)] data-[requ" +
          "ired]:after:content-[\"_*\"] data-[required]:after:text-destructive"
        }
        fieldId="email"
        required
      >
        Email
      </Field.Label>
      <Field.Control
        className="
          focus-visible:[outline:none]
          focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] block
          [inline-size:100%] [max-inline-size:24rem] py-sm px-md border-[length:var(--border-width)] border-solid
          border-input rounded-md [corner-shape:squircle] bg-card text-foreground [font-size:var(--uiify-control-text)]
          [line-height:var(--leading-normal)] [transition:var(--transition-colors)] placeholder:text-muted-foreground
          focus-visible:border-primary disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50
          data-[disabled]:cursor-not-allowed data-[invalid]:border-destructive user-invalid:border-destructive
          data-[invalid]:focus-visible:outline-destructive
        "
        fieldId="email"
        required
        as="input"
        type="email"
      />
      <Field.Description
        className="m-0 text-muted-foreground text-xs [line-height:var(--leading-normal)]"
        fieldId="email"
      >
        We will only use this for account notices.
      </Field.Description>
      <Field.Error
        className="
          m-0 text-destructive text-xs [line-height:var(--leading-normal)] motion-safe:transition-opacity
          motion-safe:starting:opacity-0
        "
        fieldId="email"
      >
        Enter a valid email address.
      </Field.Error>
    </Field.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('grid gap-sm [inline-size:100%] [max-inline-size:24rem] [&_[data-part=label]]:text-foreground [&_[data-part=label]]:text-sm [&_[data-part=label]]:font-[number:var(--weight-medium)] [&_[data-part=label]]:[line-height:var(--leading-normal)] [&_[data-part=label][data-required]::after]:content-["_*"] [&_[data-part=label][data-required]::after]:text-destructive [&_[data-part=description]]:m-0 [&_[data-part=description]]:text-muted-foreground [&_[data-part=description]]:text-xs [&_[data-part=description]]:[line-height:var(--leading-normal)] [&_[data-part=error]]:m-0 [&_[data-part=error]]:text-destructive [&_[data-part=error]]:text-xs [&_[data-part=error]]:[line-height:var(--leading-normal)] motion-safe:[&_[data-part=error]]:[transition:opacity_var(--duration-fast)_var(--ease-out)] motion-safe:starting:[&_[data-part=error]]:opacity-0 text-foreground text-sm font-[number:var(--weight-medium)] [line-height:var(--leading-normal)] data-[required]:after:content-["_*"] data-[required]:after:text-destructive focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] block py-sm px-md border-[length:var(--border-width)] border-solid border-input rounded-md [corner-shape:squircle] bg-card [font-size:var(--uiify-control-text)] [transition:var(--transition-colors)] placeholder:text-muted-foreground focus-visible:border-primary disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[invalid]:border-destructive user-invalid:border-destructive data-[invalid]:focus-visible:outline-destructive m-0 text-muted-foreground text-xs text-destructive motion-safe:transition-opacity motion-safe:starting:opacity-0');

@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

Field.Root

PropTipoPadrãoDescrição
id*string—Id estável repassado por cada parte como fieldId, conectando rótulo, controle, descrição e erro na marcação do servidor.
invalidbooleanfalseMarca o campo como inválido, expondo data-invalid para estilos.
requiredbooleanfalseMarca o campo como obrigatório, expondo data-required para estilos.
disabledbooleanfalseMarca o campo como desabilitado, expondo data-disabled para estilos.
namestring—Aceito por compatibilidade de API; o nome pertence ao controle e não é renderizado na estrutura envolvente.
renderReactElement | ((props, state) => ReactElement)—Substitua o elemento renderizado preservando comportamento e conexões de acessibilidade.

Field.Label

PropTipoPadrãoDescrição
fieldId*string—O mesmo id fornecido a Field.Root, que resolve o htmlFor que aponta para o controle.
disabledbooleanfalseMarca o rótulo como desabilitado, expondo data-disabled para estilos.
requiredbooleanfalseMarca o rótulo como obrigatório, expondo data-required para estilos.

Field.Control

PropTipoPadrãoDescrição
asElementType"div"Use input, textarea, select ou um controle personalizado. Props nativas, manipuladores de eventos, style e ref são encaminhados.
type / name / autoComplete / minLength / maxLength / pattern / readOnlynative control props—As opções do input pertencem a Field.Control, incluindo type="password". A validação padrão e a semântica de FormData são preservadas.
fieldId*string—O mesmo id fornecido a Field.Root, que resolve o id do controle e suas conexões aria-labelledby e aria-describedby.
disabledbooleanfalseEncaminha disabled nativo a input, textarea e select e expõe data-disabled.
invalidbooleanfalseDefine aria-invalid e adiciona a mensagem de erro ao aria-describedby do controle.
requiredbooleanfalseEncaminha required nativo a input, textarea e select e define aria-required.
renderReactElement | ((props, state) => ReactElement)—Substitua o elemento renderizado preservando comportamento e conexões de acessibilidade.

Field.Description

PropTipoPadrãoDescrição
fieldId*string—O mesmo id fornecido a Field.Root, que resolve o id da descrição para a qual o controle aponta.
disabledbooleanfalseMarca a descrição como desabilitada, expondo data-disabled para estilos.

Field.Error

PropTipoPadrãoDescrição
fieldId*string—O mesmo id fornecido a Field.Root, que resolve o id do erro para o qual o controle aponta quando inválido.
invalidbooleanfalseRenderiza a mensagem de erro; a parte não renderiza nada enquanto isto for false.

Teclado

TeclaAção
TabMova o foco para o controle ou para fora dele
Enter / SpaceAtive quando estiver em foco (controles semelhantes a botões)

Acessibilidade

Usa HTML semântico sempre que possível. Inspecione a marcação renderizada no ambiente interativo — ARIA só é adicionado quando os elementos nativos não bastam.

Pontos de integração de estilos

data-disabled