Skip to content

FloatingInput

Form

A persistent floating label for text-like native inputs with linked guidance.

Empty and focused emailPrefilled nameLinked error and hintDisabled organization
Open preview

Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.

Usage

Use for compact text, email, password, search, telephone, or URL entry. The input reserves space above its value, and the label moves between the empty center and the top on focus/content. Native autofill and value/defaultValue use the ordinary input API. A blank-space placeholder is internal; never use it as an accessible label. Date and number controls are intentionally outside this component because their browser widgets need different layouts.

Import
import { FloatingInput } from "@/components/ui/floating-input";

Shipped as editable source in src/components/ui/floating-input.tsx in both apps. Copy the implementation below when moving it to another project. Copy the complete example from Source code for all imports and state. Add "use client" when using hooks or event handlers. See installation and theming for project setup.

Component source

The complete implementation is included in both apps. It uses the React, Radix, Lucide, and shared-core dependencies already installed in Apex. Keep your project’s semantic theme tokens when reusing it.

src/components/ui/floating-input.tsx
"use client";
import { useId, type ComponentProps } from "react";
import { cn } from "@dashboardpack/core/lib/utils";
export interface FloatingInputProps
  extends Omit<ComponentProps<"input">, "placeholder" | "type"> {
  label: string;
  type?: "text" | "email" | "password" | "search" | "tel" | "url";
  hint?: string;
  error?: string;
}
export function FloatingInput({
  label,
  id,
  type = "text",
  hint,
  error,
  className,
  required,
  ...props
}: FloatingInputProps) {
  const generated = useId();
  const inputId = id ?? generated;
  const described =
    [
      props["aria-describedby"],
      hint ? `${inputId}-hint` : null,
      error ? `${inputId}-error` : null,
    ]
      .filter(Boolean)
      .join(" ") || undefined;
  return (
    <div className={cn("min-w-0 space-y-2", className)}>
      <div className="relative">
        <input
          {...props}
          id={inputId}
          type={type}
          placeholder=" "
          required={required}
          aria-invalid={error ? true : props["aria-invalid"]}
          aria-describedby={described}
          className={cn(
            "peer h-14 w-full rounded-xl border border-input bg-background px-3 pb-2 pt-6 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
            error && "border-destructive",
          )}
        />
        <label
          htmlFor={inputId}
          className="pointer-events-none absolute start-3 top-2 text-xs text-muted-foreground transition-all peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:text-sm peer-focus:top-2 peer-focus:translate-y-0 peer-focus:text-xs motion-reduce:transition-none"
        >
          {label}
          {required && <span aria-hidden> *</span>}
        </label>
      </div>
      {hint && (
        <p id={`${inputId}-hint`} className="text-xs text-muted-foreground">
          {hint}
        </p>
      )}
      {error && (
        <p
          id={`${inputId}-error`}
          role="alert"
          className="text-xs text-destructive"
        >
          {error}
        </p>
      )}
    </div>
  );
}

API reference

Common props and defaults. Slot-specific props are named below; native elements also accept their standard HTML attributes. The component TypeScript definitions are the complete type reference.

FloatingInput props
PropType / DefaultDescription
labelstring

Default: required

Persistent visible input label.
typetext | email | password | search | tel | url

Default: text

Supported text-like input type.
hintstring

Default: —

Linked guidance below the control.
errorstring

Default: —

Linked announced validation error.
value / defaultValue / onChangenative input props

Default: —

Standard controlled or uncontrolled input behavior.
required / disabledboolean

Default: false

Native form and availability states.
classNamestring

Default: —

Extend the container with semantic theme utilities.

Build with these too