Skip to content

Floating Label Input

An input component with a label that floats above the field when focused or filled. Uses a compound component pattern (FloatingLabel, FloatingLabel.Input, FloatingLabel.Label) for full styling control. Inspired by shadcn/ui Expansions.

Terminal window
npx shadcn@latest add https://joaocarmassi.com/components/floating-label-input.json

The direct URL serves the radix-vega variant. The Floating Label Input is style-aware — its underlying input height, radius, and label offsets differ per shadcn style. To let the CLI pick the variant that matches your project’s theme automatically, register the @prisma namespace once (see Styles & Themes) and install by name:

Terminal window
npx shadcn@latest add @prisma/floating-label-input
import { FloatingLabel } from '@/components/ui/floating-label-input';

<FloatingLabel>
<FloatingLabel.Input id='email' type='email' />
<FloatingLabel.Label htmlFor='email'>Email</FloatingLabel.Label>
</FloatingLabel>

<div className='flex flex-col gap-4'>
<FloatingLabel>
<FloatingLabel.Input id='name' />
<FloatingLabel.Label htmlFor='name'>Full Name</FloatingLabel.Label>
</FloatingLabel>
<FloatingLabel>
<FloatingLabel.Input id='email' type='email' />
<FloatingLabel.Label htmlFor='email'>Email</FloatingLabel.Label>
</FloatingLabel>
<FloatingLabel>
<FloatingLabel.Input id='password' type='password' />
<FloatingLabel.Label htmlFor='password'>Password</FloatingLabel.Label>
</FloatingLabel>
</div>

Each sub-component accepts its own className, giving full control without extra wrapper props.

<FloatingLabel>
<FloatingLabel.Input
id='username'
className='rounded-full border-primary/50 px-5 focus:border-primary'
/>
<FloatingLabel.Label htmlFor='username' className='left-3 text-primary'>
Username
</FloatingLabel.Label>
</FloatingLabel>

Wrap FloatingLabel inside a shadcn Field to add descriptions, errors, and accessible form layouts.

We'll never share your email.

import { Field, FieldDescription } from '@/components/ui/field';
<Field>
<FloatingLabel>
<FloatingLabel.Input id='email' type='email' />
<FloatingLabel.Label htmlFor='email'>Email</FloatingLabel.Label>
</FloatingLabel>
<FieldDescription>We'll never share your email.</FieldDescription>
</Field>

Use data-invalid on Field and aria-invalid on the input to show validation errors.

import { Field, FieldError } from '@/components/ui/field';
<Field data-invalid>
<FloatingLabel>
<FloatingLabel.Input id='email' type='email' aria-invalid />
<FloatingLabel.Label htmlFor='email'>Email</FloatingLabel.Label>
</FloatingLabel>
<FieldError>Enter a valid email address.</FieldError>
</Field>

A complete example with interactive validation — the error appears when the input loses focus and the value is too short.

Must be at least 3 characters.

'use client';
import { useState } from 'react';
import { FloatingLabel } from '@/components/ui/floating-label-input';
import { Field, FieldDescription, FieldError } from '@/components/ui/field';
export function FieldValidationDemo() {
const [value, setValue] = useState('');
const [touched, setTouched] = useState(false);
const isInvalid = touched && value.length < 3;
return (
<Field data-invalid={isInvalid || undefined}>
<FloatingLabel>
<FloatingLabel.Input
id='username'
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={() => setTouched(true)}
aria-invalid={isInvalid || undefined}
/>
<FloatingLabel.Label htmlFor='username'>Username</FloatingLabel.Label>
</FloatingLabel>
<FieldDescription>Must be at least 3 characters.</FieldDescription>
{isInvalid && (
<FieldError>Username must be at least 3 characters.</FieldError>
)}
</Field>
);
}

Group multiple floating label inputs inside a FieldSet with FieldGroup for a complete form layout.

Account Information

Fill in your account details below.

We'll send a confirmation to this address.

Must be at least 8 characters.

import {
Field,
FieldDescription,
FieldGroup,
FieldSet,
FieldLegend,
} from '@/components/ui/field';
<FieldSet>
<FieldLegend>Account Information</FieldLegend>
<FieldDescription>Fill in your account details below.</FieldDescription>
<FieldGroup>
<Field>
<FloatingLabel>
<FloatingLabel.Input id='name' />
<FloatingLabel.Label htmlFor='name'>Full Name</FloatingLabel.Label>
</FloatingLabel>
</Field>
<Field>
<FloatingLabel>
<FloatingLabel.Input id='email' type='email' />
<FloatingLabel.Label htmlFor='email'>Email</FloatingLabel.Label>
</FloatingLabel>
<FieldDescription>
We'll send a confirmation to this address.
</FieldDescription>
</Field>
<Field>
<FloatingLabel>
<FloatingLabel.Input id='password' type='password' />
<FloatingLabel.Label htmlFor='password'>Password</FloatingLabel.Label>
</FloatingLabel>
<FieldDescription>Must be at least 8 characters.</FieldDescription>
</Field>
</FieldGroup>
</FieldSet>

Prop Type Default Description
children React.ReactNode Must contain .Input and .Label.
className string Additional CSS classes for the root wrapper.
Prop Type Default Description
...props InputHTMLAttributes All standard input props.
className string Additional CSS classes.
Prop Type Default Description
htmlFor string Matches the input id.
children React.ReactNode The label text.
className string Additional CSS classes.

The Floating Label Input ships in a dedicated variant for each supported shadcn style. Installed through the @prisma namespace, the CLI delivers the one matching the style in your components.json — same API, with the input sizing and label offsets adjusted per style:

radix-vega
radix-nova
radix-maia
radix-lyra
radix-mira
radix-luma
radix-rhea
radix-sera

See Styles & Themes for setup and the full list of supported styles.