{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "float-label",
  "title": "Float Label",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/float-label/FloatLabel.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface FloatLabelProps extends HTMLAttributes<HTMLDivElement> {\n    label: string\n    required?: boolean\n    disabled?: boolean\n    ref?: HTMLDivElement | null\n  }\n\n  // Client-only counter for generated control ids. Assigned in onMount (never\n  // during SSR) so server and client markup stay identical.\n  let nextControlId = 0\n</script>\n\n<script lang=\"ts\">\n  import { onMount } from 'svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    label,\n    required = false,\n    disabled = false,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: FloatLabelProps = $props()\n\n  let isFocused = $state(false)\n  let hasValue = $state(false)\n  let controlId = $state<string | undefined>(undefined)\n\n  const isFloating = $derived(isFocused || hasValue)\n\n  function checkValue(target: EventTarget | null) {\n    const el = target as HTMLElement | null\n    if (!el) return\n    if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n      hasValue = !!el.value\n    } else if (el instanceof HTMLSelectElement) {\n      hasValue = !!el.value\n    } else {\n      const input = el.querySelector?.('input, textarea, select') as\n        | HTMLInputElement\n        | HTMLTextAreaElement\n        | HTMLSelectElement\n        | null\n      if (input) hasValue = !!input.value\n    }\n  }\n\n  function handleFocusin(event: FocusEvent) {\n    isFocused = true\n    checkValue(event.target)\n  }\n\n  function handleFocusout(event: FocusEvent) {\n    isFocused = false\n    checkValue(event.target)\n  }\n\n  function handleInput(event: Event) {\n    checkValue(event.target)\n  }\n\n  // Check for prefilled value on mount + associate label with the control.\n  onMount(() => {\n    if (!ref) return\n    const input = ref.querySelector('input, textarea, select') as\n      | HTMLInputElement\n      | HTMLTextAreaElement\n      | HTMLSelectElement\n      | null\n    if (!input) return\n    hasValue = !!input.value\n    if (input.id) {\n      controlId = input.id\n    } else {\n      nextControlId += 1\n      input.id = `float-label-${nextControlId}`\n      controlId = input.id\n    }\n  })\n\n  const wrapperClasses = $derived(cn('relative flex flex-col', disabled && 'cursor-not-allowed opacity-50', className))\n\n  const labelClasses = $derived(\n    cn(\n      'text-muted-foreground pointer-events-none absolute left-3 z-10 bg-transparent px-1 text-sm transition-[color,background-color,top,translate,scale] duration-200',\n      !isFloating && 'top-1/2 -translate-y-1/2',\n      isFloating && 'bg-background text-foreground top-0 -translate-y-1/2 scale-75',\n      isFocused && 'text-ring',\n      required && \"after:text-destructive after:ml-0.5 after:content-['*']\",\n    ),\n  )\n</script>\n\n<div\n  bind:this={ref}\n  class={wrapperClasses}\n  data-uipkge\n  data-slot=\"float-label\"\n  data-floating={isFloating}\n  onfocusin={handleFocusin}\n  onfocusout={handleFocusout}\n  oninput={handleInput}\n  onchange={handleInput}\n  {...restProps}\n>\n  <label for={controlId} class={labelClasses}>\n    {label}\n  </label>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/float-label/FloatLabel.svelte"
    },
    {
      "path": "packages/registry-svelte/components/float-label/index.ts",
      "content": "export { default as FloatLabel, type FloatLabelProps } from './FloatLabel.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/float-label/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Floating label wrapper for any input element. The label floats up when the input is focused or has a value. Wrap it around any input, select, or textarea. Supports required indicator and disabled state.",
  "categories": [
    "control",
    "form"
  ]
}