Mikkel Manniche
Dansk English Deutsch

Kontroller

Floating input

Et tekstfelt, hvor etiketten svæver op ved fokus, med hjælpetekst og fejltilstand. Kun CSS.

Tilpasset fra Watermelon UI (MIT) og skrevet om med tastatur, etiketter og reduceret bevægelse.

Forhåndsvisning

Brug

floating-input-demo.tsx
import { useState } from 'react'
import { FloatingInput } from '@/components/floating-input'

export default function FloatingInputDemo() {
  const [zip, setZip] = useState('')
  const bad = zip !== '' && !/^\d{4,5}$/.test(zip)

  return (
    <form className="mx-auto flex w-full max-w-sm flex-col gap-4" onSubmit={(e) => e.preventDefault()}>
      <FloatingInput label="Full name" autoComplete="name" />
      <FloatingInput label="Email" type="email" autoComplete="email" hint="We send the receipt here." />
      <FloatingInput
        label="Postcode"
        inputMode="numeric"
        autoComplete="postal-code"
        value={zip}
        onChange={(e) => setZip(e.target.value)}
        error={bad ? 'Use 4 or 5 digits.' : undefined}
      />
    </form>
  )
}

Kilde

floating-input.tsx
// Based on Watermelon UI's “Floating input” (MIT, © 2026 Watermelon Platform Contributors,
// github.com/WatermelonCorp/watermelon-platform). Rewritten in CSS only, with a linked label, hint and error.
import { useId, type InputHTMLAttributes } from 'react'
import { cn } from '@/lib/utils'

export type FloatingInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'placeholder'> & {
  label: string
  hint?: string
  /** Shown in red under the field, and marks the field invalid. */
  error?: string
}

/** A text field whose label sits inside it and floats up into the border when you type or focus. */
export function FloatingInput({ label, hint, error, className, id, ...props }: FloatingInputProps) {
  const own = useId()
  const fieldId = id ?? own
  const note = error ?? hint

  return (
    <div className={cn('w-full', className)}>
      <div className="relative">
        <input
          id={fieldId}
          placeholder=" "
          aria-invalid={error ? true : undefined}
          aria-describedby={note ? `${fieldId}-note` : undefined}
          className={cn(
            'peer h-12 w-full rounded-xl border bg-background px-4 pt-1 outline-none transition-[border-color,box-shadow] duration-200',
            'focus:border-ring focus:ring-3 focus:ring-ring/20',
            error && 'border-destructive focus:border-destructive focus:ring-destructive/20',
          )}
          {...props}
        />
        <label
          htmlFor={fieldId}
          className={cn(
            'pointer-events-none absolute top-0 left-3 origin-left -translate-y-1/2 bg-background px-1 text-xs text-muted-foreground transition-all duration-200 ease-out',
            'peer-placeholder-shown:top-1/2 peer-placeholder-shown:text-base',
            'peer-focus:top-0 peer-focus:text-xs peer-focus:text-ring',
            error && 'text-destructive peer-focus:text-destructive',
            'motion-reduce:transition-none',
          )}
        >
          {label}
        </label>
      </div>
      {note && (
        <p id={`${fieldId}-note`} className={cn('mt-1.5 px-1 text-xs', error ? 'text-destructive' : 'text-muted-foreground')}>
          {note}
        </p>
      )}
    </div>
  )
}

Installér

npx shadcn@latest add https://mikkelmanniche.dk/lab/r/floating-input.json

Med registret tilføjet (components.json)

npx shadcn@latest add @manniche/floating-input

Afhængigheder

Filer

  • floating-input.tsx

Manniche Lab · open source

Gratis værktøjer til AI-agenter og webudvikling.