Ir al contenido

Field

Envoltura de etiqueta, control y error.

Navegadores
Chrome 123+Firefox 121+Safari 17.5+
Nivel
0
Tamaño comprimido
668 B
Presupuesto de diseño forzado
0 ms
Presupuesto de axe
0
Compatible con SSR
Sí
Contrato publicado
Entorno de pruebas

Zona de pruebas

Field

Default

Your display name

Invalid

Password

Use at least eight characters.

Message

Up to 500 characters.

Disabled

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

PropTipoPredeterminadoDescripción
id*string—Id estable que cada parte recibe como fieldId para conectar etiqueta, control, descripción y error en el marcado del servidor.
invalidbooleanfalseMarca el campo como no válido y expone data-invalid para aplicar estilos.
requiredbooleanfalseMarca el campo como obligatorio y expone data-required para aplicar estilos.
disabledbooleanfalseMarca el campo como desactivado y expone data-disabled para aplicar estilos.
namestring—Se acepta por compatibilidad con la API; el nombre corresponde al control y no se renderiza en la envoltura.
renderReactElement | ((props, state) => ReactElement)—Reemplaza el elemento renderizado y conserva las conexiones de comportamiento y accesibilidad.

Field.Label

PropTipoPredeterminadoDescripción
fieldId*string—El mismo id que recibe Field.Root; resuelve el htmlFor que apunta al control.
disabledbooleanfalseMarca la etiqueta como desactivada y expone data-disabled para aplicar estilos.
requiredbooleanfalseMarca la etiqueta como obligatoria y expone data-required para aplicar estilos.

Field.Control

PropTipoPredeterminadoDescripción
asElementType"div"Usa input, textarea, select o un control personalizado. Se pasan las props nativas, los manejadores de eventos, style y ref.
type / name / autoComplete / minLength / maxLength / pattern / readOnlynative control props—Las opciones de input corresponden a Field.Control, incluido type="password". Se conserva la semántica estándar de validación y FormData.
fieldId*string—El mismo id que recibe Field.Root; resuelve el id del control y sus conexiones aria-labelledby y aria-describedby.
disabledbooleanfalsePasa disabled nativo a input, textarea y select, y expone data-disabled.
invalidbooleanfalseEstablece aria-invalid y añade el mensaje de error al aria-describedby del control.
requiredbooleanfalsePasa required nativo a input, textarea y select, y establece aria-required.
renderReactElement | ((props, state) => ReactElement)—Reemplaza el elemento renderizado y conserva las conexiones de comportamiento y accesibilidad.

Field.Description

PropTipoPredeterminadoDescripción
fieldId*string—El mismo id que recibe Field.Root; resuelve el id de la descripción al que apunta el control.
disabledbooleanfalseMarca la descripción como desactivada y expone data-disabled para aplicar estilos.

Field.Error

PropTipoPredeterminadoDescripción
fieldId*string—El mismo id que recibe Field.Root; resuelve el id del error al que apunta el control cuando no es válido.
invalidbooleanfalseRenderiza el mensaje de error; la parte no renderiza nada mientras este valor sea false.

Teclado

TeclaAcción
TabLlevar el foco al control o sacarlo de él
Enter / SpaceActivar cuando tiene el foco (controles de tipo botón)

Accesibilidad

Usa HTML semántico siempre que es posible. Inspecciona el marcado renderizado en el entorno de pruebas: ARIA se añade solo cuando los elementos nativos no bastan.

Puntos de estilo

data-disabled
Esta página se tradujo automáticamente y aún no se ha revisado.