{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"search-field","type":"registry:ui","title":"Search field","description":"A recognizable search entry point with clear affordances.","categories":["inputs"],"dependencies":["lucide-react","motion"],"registryDependencies":["https://ui.teamofsilicons.com/r/silicon-motion-tokens.json"],"files":[{"path":"registry/components/search-field/search-field.tsx","type":"registry:component","target":"@components/silicon-ui/search-field/search-field.tsx","content":"\"use client\";\nimport { forwardRef, useId, useRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { Search, X as Xmark } from \"lucide-react\";\nimport { motionTokens } from \"../lib/motion-tokens\";\nimport styles from \"./search-field.module.css\";\nexport interface SearchFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> { label: string; value: string; onValueChange: (value: string) => void }\nexport const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(function SearchField({ label, value, onValueChange, id, className, ...props }, ref) {\n  const generated = useId(); const controlId = id ?? generated; const reduced = useReducedMotion(); const inputRef = useRef<HTMLInputElement | null>(null);\n  const setRefs = (node: HTMLInputElement | null) => { inputRef.current = node; if (typeof ref === \"function\") ref(node); else if (ref) ref.current = node; };\n  // Clearing returns focus to the field, since the clear button unmounts under the pointer.\n  function clear() { onValueChange(\"\"); inputRef.current?.focus(); }\n  return <div className={styles.field}><label htmlFor={controlId}>{label}</label><div className={styles.shell} data-filled={value ? \"true\" : undefined}>\n    <Search width={18} height={18} aria-hidden=\"true\"/><input {...props} ref={setRefs} id={controlId} type=\"search\" value={value} onChange={event => onValueChange(event.target.value)} className={[styles.input, className].filter(Boolean).join(\" \")} />\n    {/* The clear button has a reserved slot, so the field never changes width when it appears or leaves. */}\n    <span className={styles.clearSlot}><AnimatePresence initial={false}>{value ? <motion.button key=\"clear\" type=\"button\" tabIndex={0} onClick={clear} aria-label=\"Clear search\" initial={reduced ? { opacity: 0 } : { opacity: 0, scale: .8, filter: `blur(${motionTokens.blur.subtle}px)` }} animate={{ opacity: 1, scale: 1, filter: \"blur(0px)\" }} exit={reduced ? { opacity: 0, transition: { duration: 0 } } : { opacity: 0, scale: .8, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.instant, ease: [...motionTokens.ease.standard] } }} whileTap={reduced ? undefined : { scale: .96, transition: { duration: motionTokens.duration.instant, ease: [...motionTokens.ease.standard] } }} transition={reduced ? { duration: motionTokens.duration.instant } : { ...motionTokens.spring.snappy, opacity: { duration: motionTokens.duration.fast }, filter: { duration: motionTokens.duration.fast } }}><Xmark width={16} height={16} aria-hidden=\"true\" /></motion.button> : null}</AnimatePresence></span>\n  </div></div>;\n});\n"},{"path":"registry/components/search-field/search-field.module.css","type":"registry:component","target":"@components/silicon-ui/search-field/search-field.module.css","content":".field { display: grid; gap: var(--space-related); min-width: 0; }\n.field label { font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); letter-spacing: var(--tracking-body); }\n/* Focus darkens the border; the shell never changes size. */\n.shell { display: flex; min-height: var(--control-height-md); align-items: center; gap: var(--space-related); border: 1px solid var(--border-strong); border-radius: var(--radius-control); padding: 0 var(--space-group); background: var(--surface); transition: border-color var(--duration-fast) var(--ease-standard); }\n@media (hover: hover) and (pointer: fine) { .shell:hover:not(:focus-within) { border-color: var(--foreground); } }\n.shell:focus-within { border-color: var(--foreground); }\n/* The glass wakes up with the field: muted at rest, full contrast while searching. */\n.shell > svg { flex: 0 0 auto; color: var(--text-muted); transition: color var(--duration-fast) var(--ease-standard); }\n.shell:focus-within > svg, .shell[data-filled=\"true\"] > svg { color: var(--foreground); }\n.input { width: 100%; min-width: 0; border: 0; background: transparent; color: var(--foreground); font: inherit; font-size: var(--text-sm); letter-spacing: var(--tracking-body); outline: 0; }\n.input:focus-visible { outline: 0; }\n.input::placeholder { color: var(--text-muted); }\n.input::-webkit-search-cancel-button { display: none; }\n.clearSlot { display: grid; width: 24px; height: 24px; flex: 0 0 auto; place-items: center; }\n.shell button { display: grid; width: 24px; height: 24px; flex: 0 0 auto; place-items: center; border: 0; border-radius: var(--radius-control); background: transparent; color: var(--text-muted); transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }\n@media (hover: hover) and (pointer: fine) { .shell button:hover { background: var(--surface-muted); color: var(--foreground); } }\n@media (prefers-reduced-motion: reduce) { .shell, .shell > svg, .shell button { transition: none; } }\n"}],"docs":"Docs and live preview: https://ui.teamofsilicons.com/search-field","meta":{"tier":"free","kind":"component","docs":"https://ui.teamofsilicons.com/search-field","markdown":"https://ui.teamofsilicons.com/search-field.md","tags":["field","search"],"status":"stable"}}