{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"text-field-clean","type":"registry:ui","title":"Text Field (Clean)","description":"One input for every form – label, hint and error in place, prefix and suffix, clear, show-password and copy buttons, a character count","dependencies":[],"files":[{"path":"registry/text-field-clean/text-field-clean.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useId, useRef, useState, type ComponentPropsWithRef, type ReactNode } from \"react\";\n\n/*\n * Text Field – a single self-contained file, clean style.\n * One input for every form: a label, an optional hint, prefix and suffix text or icons, a clear, reveal or copy\n * button, a character count and an error that takes the hint's place. White with a hairline shadow; focus only\n * firms up the edge – no ring. The styles are injected below, so no extra files are needed.\n */\n\nexport type TextFieldProps = Omit<ComponentPropsWithRef<\"input\">, \"size\" | \"prefix\"> & {\n  /** Shown above the field and tied to it */\n  label?: ReactNode;\n  /** A quiet line under the field – the error takes its place while there is one */\n  hint?: ReactNode;\n  /** Marks the field invalid and shows this under it, in place of the hint */\n  error?: ReactNode;\n  /** Text or an icon inside the field, before the value – e.g. \"https://\" or a search icon */\n  prefix?: ReactNode;\n  /** Text or an icon inside the field, after the value – e.g. \".acme.app\" or \"USD\" */\n  suffix?: ReactNode;\n  /** sm 28 · md 32 · lg 36 high */\n  size?: \"sm\" | \"md\" | \"lg\";\n  /** \"Optional\" next to the label */\n  optional?: boolean;\n  /** A small × that empties the field while it has a value */\n  clearable?: boolean;\n  /** A copy button – for API keys, links and IDs, usually with readOnly */\n  copyable?: boolean;\n  /** Shows \"12/40\" under the field; needs maxLength */\n  counter?: boolean;\n  /** Called after the × emptied the field (onChange fires too) */\n  onClear?: () => void;\n  /** Extra class on the input itself; className goes on the wrapper */\n  inputClassName?: string;\n};\n\nexport function TextField({\n  label,\n  hint,\n  error,\n  prefix,\n  suffix,\n  size = \"md\",\n  optional = false,\n  clearable = false,\n  copyable = false,\n  counter = false,\n  onClear,\n  onKeyDown,\n  className,\n  inputClassName,\n  id,\n  type = \"text\",\n  value,\n  defaultValue,\n  onChange,\n  disabled,\n  readOnly,\n  maxLength,\n  ref,\n  \"aria-describedby\": describedBy,\n  ...rest\n}: TextFieldProps) {\n  const autoId = useId();\n  const inputId = id ?? `${autoId}-input`;\n  const messageId = `${autoId}-message`;\n  const counterId = `${autoId}-count`;\n  const input = useRef<HTMLInputElement | null>(null);\n\n  // The current text, for the count and the × – follows value when controlled, the input itself otherwise\n  const [own, setOwn] = useState(String(defaultValue ?? \"\"));\n  const text = value !== undefined && value !== null ? String(value) : own;\n\n  const isPassword = type === \"password\";\n  const [revealed, setRevealed] = useState(false);\n  const [copied, setCopied] = useState(false);\n  const copyTimer = useRef(0);\n  useEffect(() => () => window.clearTimeout(copyTimer.current), []);\n\n  const invalid = Boolean(error) || rest[\"aria-invalid\"] === true || rest[\"aria-invalid\"] === \"true\";\n  // Only text or elements count – error={true} marks the field invalid without leaving an empty line\n  const message = shows(error) ? error : shows(hint) ? hint : null;\n  const showCount = counter && typeof maxLength === \"number\";\n  // Without a label, the placeholder names the field for screen readers\n  const fallbackName =\n    label == null && !rest[\"aria-label\"] && !rest[\"aria-labelledby\"] && typeof rest.placeholder === \"string\" ? rest.placeholder : undefined;\n\n  // An uncontrolled field follows form.reset(), so the count and the × don't show the old text\n  useEffect(() => {\n    const form = input.current?.form;\n    if (!form || value !== undefined) return;\n    const onReset = () => window.setTimeout(() => setOwn(input.current?.value ?? \"\"));\n    form.addEventListener(\"reset\", onReset);\n    return () => form.removeEventListener(\"reset\", onReset);\n  }, [value]);\n  const showClear = clearable && !disabled && !readOnly && text.length > 0;\n  const scale = SIZES.includes(size) ? size : \"md\";\n\n  const clear = () => {\n    const el = input.current;\n    if (!el) return;\n    // Sets the value the way typing would, so React's onChange runs for controlled fields too\n    Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, \"value\")?.set?.call(el, \"\");\n    el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n    el.focus();\n    onClear?.();\n  };\n\n  const copy = async () => {\n    try {\n      await navigator.clipboard.writeText(text);\n    } catch {\n      // No clipboard (an insecure page or a denied permission): select the text, so ⌘C works\n      input.current?.select();\n      return;\n    }\n    setCopied(true);\n    window.clearTimeout(copyTimer.current);\n    copyTimer.current = window.setTimeout(() => setCopied(false), 1600);\n  };\n\n  return (\n    <div\n      className={[\"ui-tfc\", className].filter(Boolean).join(\" \")}\n      data-size={scale}\n      data-invalid={invalid || undefined}\n      data-disabled={disabled || undefined}\n      data-readonly={readOnly || undefined}\n    >\n      {/* React 19 hoists this into <head> once, no matter how many fields render */}\n      <style href=\"ui-text-field-clean\" precedence=\"default\">\n        {css}\n      </style>\n      {label != null && (\n        <div className=\"ui-tfc__head\">\n          <label className=\"ui-tfc__label\" htmlFor={inputId}>\n            {label}\n          </label>\n          {optional && <span className=\"ui-tfc__optional\">Optional</span>}\n        </div>\n      )}\n      {/* A click anywhere on the box – the prefix, the padding – puts the caret in the input */}\n      <div\n        className=\"ui-tfc__box\"\n        onPointerDown={(e) => {\n          if (e.target === input.current || (e.target as HTMLElement).closest(\"button\")) return;\n          e.preventDefault();\n          input.current?.focus();\n        }}\n      >\n        {prefix != null && (\n          <span className=\"ui-tfc__affix\" data-side=\"start\">\n            {/* Long text is cut at its start – the part next to the value is the one that matters */}\n            {typeof prefix === \"string\" ? <span className=\"ui-tfc__affix-text\">{prefix}</span> : prefix}\n          </span>\n        )}\n        <input\n          {...rest}\n          ref={(el) => {\n            input.current = el;\n            if (typeof ref === \"function\") ref(el);\n            else if (ref) ref.current = el;\n          }}\n          id={inputId}\n          className={[\"ui-tfc__input\", inputClassName].filter(Boolean).join(\" \")}\n          type={isPassword && revealed ? \"text\" : type}\n          value={value}\n          defaultValue={defaultValue}\n          disabled={disabled}\n          readOnly={readOnly}\n          maxLength={maxLength}\n          aria-label={rest[\"aria-label\"] ?? fallbackName}\n          aria-invalid={invalid || undefined}\n          aria-describedby={[describedBy, message ? messageId : null, showCount ? counterId : null].filter(Boolean).join(\" \") || undefined}\n          dir={rest.dir ?? \"auto\"}\n          data-revealed={isPassword ? (revealed ? \"true\" : \"false\") : undefined}\n          onKeyDown={(e) => {\n            // Esc empties a clearable field, like a search field on macOS and iOS\n            if (e.key === \"Escape\" && showClear) {\n              e.preventDefault();\n              clear();\n            }\n            onKeyDown?.(e);\n          }}\n          onChange={(e) => {\n            if (value === undefined) setOwn(e.target.value);\n            onChange?.(e);\n          }}\n        />\n        {suffix != null && <span className=\"ui-tfc__affix\" data-side=\"end\">{suffix}</span>}\n        {showClear && (\n          <button type=\"button\" className=\"ui-tfc__action\" aria-label=\"Clear\" tabIndex={-1} onClick={clear}>\n            <ClearIcon />\n          </button>\n        )}\n        {isPassword && !disabled && (\n          <button\n            type=\"button\"\n            className=\"ui-tfc__action\"\n            aria-label={revealed ? \"Hide password\" : \"Show password\"}\n            aria-pressed={revealed}\n            onClick={() => setRevealed((r) => !r)}\n          >\n            <span className=\"ui-tfc__swap\" key={revealed ? \"hide\" : \"show\"}>\n              {revealed ? <EyeOffIcon /> : <EyeIcon />}\n            </span>\n          </button>\n        )}\n        {copyable && (\n          <button\n            type=\"button\"\n            className=\"ui-tfc__action\"\n            data-copied={copied || undefined}\n            aria-label={copied ? \"Copied\" : \"Copy\"}\n            onClick={copy}\n            disabled={disabled || !text}\n          >\n            <span className=\"ui-tfc__swap\" key={copied ? \"done\" : \"copy\"}>\n              {copied ? <CheckIcon /> : <CopyIcon />}\n            </span>\n          </button>\n        )}\n        {copyable && (\n          <span className=\"ui-tfc__sr\" role=\"status\">\n            {copied ? \"Copied\" : \"\"}\n          </span>\n        )}\n      </div>\n      {(message || showCount) && (\n        <div className=\"ui-tfc__foot\">\n          {/* The error swaps in where the hint was, so the form below doesn't move */}\n          <p className=\"ui-tfc__message\" id={messageId} key={shows(error) ? \"error\" : \"hint\"} dir=\"auto\" aria-live={shows(error) ? \"polite\" : undefined}>\n            {message}\n          </p>\n          {showCount && (\n            <span\n              className=\"ui-tfc__count\"\n              id={counterId}\n              data-full={text.length === maxLength || undefined}\n              data-over={text.length > maxLength || undefined}\n            >\n              {text.length}/{maxLength}\n            </span>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n\nconst SIZES = [\"sm\", \"md\", \"lg\"];\n/** Text or an element – not true, false, null or an empty string */\nconst shows = (v: ReactNode) => v != null && typeof v !== \"boolean\" && v !== \"\";\n\n/* Icons: Hugeicons Free (MIT), drawn at 16px */\nfunction ClearIcon() {\n  return (\n    <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\">\n      <path d=\"M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm3.53 12.22a.75.75 0 1 1-1.06 1.06L12 13.06l-2.47 2.47a.75.75 0 0 1-1.06-1.06L10.94 12 8.47 9.53a.75.75 0 0 1 1.06-1.06L12 10.94l2.47-2.47a.75.75 0 1 1 1.06 1.06L13.06 12l2.47 2.47Z\" />\n    </svg>\n  );\n}\nfunction EyeIcon() {\n  return (\n    <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n      <path d=\"M21.544 11.045C21.848 11.471 22 11.685 22 12c0 .316-.152.529-.456.955C20.178 14.871 16.689 19 12 19c-4.69 0-8.178-4.13-9.544-6.045C2.152 12.529 2 12.315 2 12c0-.316.152-.529.456-.955C3.822 9.129 7.311 5 12 5c4.69 0 8.178 4.13 9.544 6.045Z\" />\n      <path d=\"M15 12a3 3 0 1 0-6 0 3 3 0 0 0 6 0Z\" />\n    </svg>\n  );\n}\nfunction EyeOffIcon() {\n  return (\n    <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n      <path d=\"M19.439 15.439c.996-.91 1.719-1.82 2.105-2.36.304-.426.456-.64.456-.955 0-.316-.152-.529-.456-.955C20.178 9.253 16.689 5.124 12 5.124c-.908 0-1.77.155-2.582.418M6.74 6.869c-2.09 1.267-3.6 3.083-4.284 4.04-.304.426-.456.64-.456.955 0 .316.152.529.456.955C3.822 14.735 7.311 18.864 12 18.864c2.008 0 3.786-.757 5.26-1.746\" />\n      <path d=\"M9.858 10a2.98 2.98 0 0 0-.858 2.124 3 3 0 0 0 5.124 2.12M3 3l18 18\" />\n    </svg>\n  );\n}\nfunction CopyIcon() {\n  return (\n    <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n      <path d=\"M9 15c0-2.828 0-4.243.879-5.121C10.757 9 12.172 9 15 9h1c2.828 0 4.243 0 5.121.879C22 10.757 22 12.172 22 15v1c0 2.828 0 4.243-.879 5.121C20.243 22 18.828 22 16 22h-1c-2.828 0-4.243 0-5.121-.879C9 20.243 9 18.828 9 16v-1Z\" />\n      <path d=\"M17 9c-.003-2.957-.047-4.489-.908-5.538a4 4 0 0 0-.554-.554C14.43 2 12.788 2 9.5 2c-3.287 0-4.931 0-6.038.908a4 4 0 0 0-.554.554C2 4.57 2 6.212 2 9.5c0 3.287 0 4.931.908 6.038a4 4 0 0 0 .554.554c1.05.86 2.58.906 5.538.908\" />\n    </svg>\n  );\n}\nfunction CheckIcon() {\n  return (\n    <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n      <path className=\"ui-tfc__check\" d=\"M5 12.5 9.5 17 19 7\" pathLength={1} />\n    </svg>\n  );\n}\n\nconst css = /* css */ `\n/* Colors and motion as variables – override them on .ui-tfc (see Theming in the docs) */\n.ui-tfc {\n  --ui-tfc-title: var(--aui-ink, rgba(0, 0, 0, 0.88));\n  --ui-tfc-ink: var(--aui-ink, rgba(0, 0, 0, 0.88));\n  --ui-tfc-muted: var(--aui-muted, rgba(0, 0, 0, 0.61));\n  --ui-tfc-subtle: var(--aui-muted, rgba(0, 0, 0, 0.45));\n  --ui-tfc-faint: var(--aui-placeholder, rgba(0, 0, 0, 0.3));\n  --ui-tfc-icon: var(--aui-muted, rgba(0, 0, 0, 0.447));\n  --ui-tfc-surface: var(--aui-surface, #ffffff);\n  --ui-tfc-wash: rgba(0, 0, 0, 0.04);\n  --ui-tfc-readonly: rgba(0, 0, 0, 0.02);\n  --ui-tfc-shadow: 0 0 0 0.5px var(--aui-border, rgba(0, 0, 0, 0.08)), 0 1px 1px -1px rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n  --ui-tfc-shadow-hover: 0 0 0 0.5px var(--aui-border-strong, rgba(0, 0, 0, 0.13)), 0 1px 1px -1px rgba(0, 0, 0, 0.12), 0 1px 2px 0 rgba(0, 0, 0, 0.06);\n  --ui-tfc-shadow-focus: 0 0 0 1px var(--aui-border-strong, rgba(0, 0, 0, 0.28)), 0 1px 2px 0 rgba(0, 0, 0, 0.06);\n  --ui-tfc-danger: var(--aui-danger, #c4320a);\n  --ui-tfc-danger-edge: color-mix(in srgb, var(--aui-red, #f04438) 55%, transparent);\n  --ui-tfc-danger-edge-focus: rgba(229, 72, 77, 0.8);\n  --ui-tfc-success: var(--aui-success, #1e8a4f);\n  --ui-tfc-focus: var(--aui-focus, rgba(0, 96, 226, 0.7));\n  --ui-tfc-ease: cubic-bezier(0.22, 1, 0.36, 1);\n\n  --h: 32px;\n  --px: 10px;\n  --fs: 13px;\n  --radius: var(--aui-radius, 7px);\n\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n  min-width: 0;\n  font-family: var(--aui-font, \"Inter\", \"InterVariable\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n  font-size: 13px;\n  line-height: 20px;\n  color: var(--ui-tfc-ink);\n  -webkit-font-smoothing: antialiased;\n}\n.ui-tfc[data-size=\"sm\"] { --h: 28px; --px: 8px; --fs: 13px; --radius: calc(var(--aui-radius, 7px) - 1px); }\n.ui-tfc[data-size=\"lg\"] { --h: 36px; --px: 12px; --fs: 14px; --radius: calc(var(--aui-radius, 7px) + 1px); }\n/* Touch screens: a comfortable tap height, and 16px text so iOS doesn't zoom in on focus */\n@media (pointer: coarse) {\n  .ui-tfc { --fs: 16px; }\n  .ui-tfc[data-size=\"sm\"] { --h: 34px; }\n  .ui-tfc[data-size=\"md\"] { --h: 38px; }\n  .ui-tfc[data-size=\"lg\"] { --h: 40px; }\n}\n\n.ui-tfc__head { display: flex; align-items: baseline; gap: 6px; min-width: 0; }\n.ui-tfc__label { min-width: 0; font-weight: 500; color: var(--ui-tfc-title); overflow-wrap: anywhere; cursor: default; }\n.ui-tfc__optional { flex: none; font-size: 12px; line-height: 16px; color: var(--ui-tfc-subtle); }\n\n/* The box: white, a hairline shadow as its edge; hover and focus only make that edge firmer */\n.ui-tfc__box {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  box-sizing: border-box;\n  height: var(--h);\n  min-width: 0;\n  padding-block: 0;\n  padding-inline: var(--px) calc(var(--px) - 4px);\n  border-radius: var(--radius);\n  background: var(--ui-tfc-surface);\n  box-shadow: var(--ui-tfc-shadow);\n  cursor: text;\n  transition: box-shadow 140ms ease, background-color 140ms ease;\n}\n/* Without a button at the end, both sides sit at the same distance */\n.ui-tfc__box:not(:has(.ui-tfc__action)) { padding-inline-end: var(--px); }\n.ui-tfc__box:hover { box-shadow: var(--ui-tfc-shadow-hover); }\n.ui-tfc__box:focus-within { box-shadow: var(--ui-tfc-shadow-focus); }\n.ui-tfc[data-invalid] .ui-tfc__box { box-shadow: 0 0 0 1px var(--ui-tfc-danger-edge), 0 1px 2px 0 rgba(0, 0, 0, 0.05); }\n.ui-tfc[data-invalid] .ui-tfc__box:focus-within { box-shadow: 0 0 0 1px var(--ui-tfc-danger-edge-focus), 0 1px 2px 0 rgba(0, 0, 0, 0.05); }\n.ui-tfc[data-readonly] .ui-tfc__box { background: var(--ui-tfc-readonly); box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08); cursor: default; }\n.ui-tfc[data-disabled] .ui-tfc__box { background: var(--ui-tfc-readonly); box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.06); cursor: not-allowed; }\n.ui-tfc[data-disabled] { opacity: 0.55; }\n.ui-tfc[data-invalid] .ui-tfc__box { animation: ui-tfc-nudge 320ms var(--ui-tfc-ease); }\n\n.ui-tfc__input {\n  flex: 1;\n  min-width: 0;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n  border: 0;\n  outline: 0;\n  background: transparent;\n  font: inherit;\n  font-size: var(--fs);\n  color: var(--ui-tfc-ink);\n  text-overflow: ellipsis;\n  -webkit-appearance: none;\n  appearance: none;\n}\n/* Next to a prefix or suffix the value keeps at least 6em – they shrink first */\n.ui-tfc__box:has(> .ui-tfc__affix) .ui-tfc__input { flex: 1 0 6em; min-width: 6em; }\n.ui-tfc__input::placeholder { color: var(--ui-tfc-faint); opacity: 1; }\n.ui-tfc__input:disabled { cursor: not-allowed; }\n.ui-tfc__input[type=\"search\"]::-webkit-search-cancel-button { display: none; }\n.ui-tfc__input[type=\"number\"] { -moz-appearance: textfield; font-variant-numeric: tabular-nums; }\n.ui-tfc__input[type=\"number\"]::-webkit-inner-spin-button,\n.ui-tfc__input[type=\"number\"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }\n/* Revealing a password: the characters come out of a short blur */\n.ui-tfc__input[data-revealed] { animation: ui-tfc-reveal 260ms var(--ui-tfc-ease); }\n.ui-tfc__input[data-revealed=\"false\"] { animation-name: ui-tfc-reveal-hide; }\n\n/* Prefix and suffix: quiet text or an icon inside the box */\n.ui-tfc__affix {\n  display: inline-flex;\n  flex: 0 1 auto;\n  align-items: center;\n  min-width: 0;\n  max-width: 70%;\n  font-size: var(--fs);\n  color: var(--ui-tfc-subtle);\n  white-space: nowrap;\n  user-select: none;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n/* Text right against the value, as if it were part of it (\"https://\" + \"acme\") */\n.ui-tfc__affix[data-side=\"start\"] { margin-inline-end: -6px; }\n.ui-tfc__affix[data-side=\"end\"] { max-width: 40%; margin-inline-start: -6px; }\n/* A long prefix loses its start: \"…/workspaces/\" stays next to the value */\n.ui-tfc__affix-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.ui-tfc__affix[data-side=\"start\"]:dir(ltr) .ui-tfc__affix-text { direction: rtl; text-align: left; }\n.ui-tfc__affix[data-side=\"start\"]:dir(ltr) .ui-tfc__affix-text::after { content: \"\\\\200E\"; }\n/* An icon keeps its distance */\n.ui-tfc__affix[data-side]:has(> svg) { margin: 0; color: var(--ui-tfc-icon); }\n.ui-tfc__affix > svg { display: block; width: 16px; height: 16px; flex: none; }\n\n/* Clear, reveal and copy: small ghost buttons at the end */\n.ui-tfc__action {\n  display: grid;\n  place-items: center;\n  flex: none;\n  width: calc(var(--h) - 8px);\n  height: calc(var(--h) - 8px);\n  position: relative;\n  max-width: 26px;\n  max-height: 26px;\n  margin: 0;\n  padding: 0;\n  border: 0;\n  border-radius: 5px;\n  background: transparent;\n  color: var(--ui-tfc-icon);\n  cursor: pointer;\n  -webkit-tap-highlight-color: transparent;\n  transition: background-color 140ms ease, color 140ms ease;\n}\n.ui-tfc__action + .ui-tfc__action { margin-left: -4px; }\n/* Touch screens: bigger buttons and a hit area that reaches past them */\n@media (pointer: coarse) {\n  .ui-tfc__action { width: 32px; height: 32px; max-width: none; max-height: none; }\n  .ui-tfc__action::before { content: \"\"; position: absolute; inset: -4px; }\n}\n.ui-tfc__action:hover:not(:disabled) { background: var(--ui-tfc-wash); color: var(--ui-tfc-ink); }\n.ui-tfc__action:focus-visible { outline: 2px solid var(--ui-tfc-focus); outline-offset: -2px; }\n.ui-tfc__action:disabled { opacity: 0.4; cursor: default; }\n.ui-tfc__action[data-copied] { color: var(--ui-tfc-success); }\n.ui-tfc__action svg { display: block; }\n.ui-tfc__swap { display: grid; animation: ui-tfc-swap 280ms var(--ui-tfc-ease); }\n.ui-tfc__check { stroke-dasharray: 1; animation: ui-tfc-draw 320ms 60ms var(--ui-tfc-ease) both; }\n\n/* Under the field: the hint, or the error in its place, and the count */\n.ui-tfc__foot { display: flex; align-items: flex-start; gap: 12px; min-width: 0; }\n.ui-tfc__message {\n  flex: 1;\n  min-width: 0;\n  margin: 0;\n  font-size: 12px;\n  line-height: 16px;\n  color: var(--ui-tfc-subtle);\n  overflow-wrap: anywhere;\n  animation: ui-tfc-message 260ms var(--ui-tfc-ease);\n}\n.ui-tfc[data-invalid] .ui-tfc__message { color: var(--ui-tfc-danger); }\n.ui-tfc__count { flex: none; margin-left: auto; font-size: 12px; line-height: 16px; font-variant-numeric: tabular-nums; color: var(--ui-tfc-faint); transition: color 140ms ease; }\n.ui-tfc__count[data-over] { color: var(--ui-tfc-danger); }\n.ui-tfc__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }\n.ui-tfc__count[data-full] { color: var(--ui-tfc-muted); }\n\n@keyframes ui-tfc-nudge {\n  30% { transform: translateX(-3px); }\n  65% { transform: translateX(2px); }\n}\n@keyframes ui-tfc-message {\n  from { opacity: 0; transform: translateY(-3px); filter: blur(2px); }\n}\n@keyframes ui-tfc-reveal {\n  from { filter: blur(4px); opacity: 0.4; }\n}\n@keyframes ui-tfc-reveal-hide {\n  from { filter: blur(3px); opacity: 0.5; }\n}\n@keyframes ui-tfc-swap {\n  from { opacity: 0; transform: scale(0.6); filter: blur(2px); }\n}\n@keyframes ui-tfc-draw {\n  from { stroke-dashoffset: 1; }\n  to { stroke-dashoffset: 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ui-tfc *, .ui-tfc[data-invalid] .ui-tfc__box { animation-duration: 1ms !important; }\n  .ui-tfc__box { transition-duration: 1ms; }\n}\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-tfc {\n  --ui-tfc-title: var(--aui-ink, rgba(255, 255, 255, 0.92));\n  --ui-tfc-ink: var(--aui-ink, rgba(255, 255, 255, 0.9));\n  --ui-tfc-muted: var(--aui-muted, rgba(255, 255, 255, 0.62));\n  --ui-tfc-subtle: var(--aui-muted, rgba(255, 255, 255, 0.46));\n  --ui-tfc-faint: var(--aui-placeholder, rgba(255, 255, 255, 0.3));\n  --ui-tfc-icon: var(--aui-muted, rgba(255, 255, 255, 0.5));\n  --ui-tfc-surface: var(--aui-surface, #1c1c1e);\n  --ui-tfc-wash: rgba(255, 255, 255, 0.07);\n  --ui-tfc-readonly: rgba(255, 255, 255, 0.03);\n  --ui-tfc-shadow: 0 0 0 0.5px var(--aui-border, rgba(255, 255, 255, 0.14)), 0 1px 2px 0 rgba(0, 0, 0, 0.4);\n  --ui-tfc-shadow-hover: 0 0 0 0.5px var(--aui-border-strong, rgba(255, 255, 255, 0.2)), 0 1px 2px 0 rgba(0, 0, 0, 0.4);\n  --ui-tfc-shadow-focus: 0 0 0 1px var(--aui-border-strong, rgba(255, 255, 255, 0.3)), 0 1px 2px 0 rgba(0, 0, 0, 0.4);\n  --ui-tfc-danger: var(--aui-danger, #ff8a7a);\n  --ui-tfc-danger-edge: color-mix(in srgb, var(--aui-red, #ff6b5f) 50%, transparent);\n  --ui-tfc-danger-edge-focus: rgba(255, 99, 105, 0.75);\n  --ui-tfc-success: var(--aui-success, #7dcf9f);\n  --ui-tfc-focus: var(--aui-focus, rgba(77, 141, 255, 0.8));\n}\n:where(.dark, [data-theme=\"dark\"]) .ui-tfc[data-readonly] .ui-tfc__box,\n:where(.dark, [data-theme=\"dark\"]) .ui-tfc[data-disabled] .ui-tfc__box { box-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12); }\n`;\n"}]}