Search Components...

Floating Label Input

An input that always shows the label and floats when the input has a value.

Loading...

Installation

Copy and paste the following code into your project.

import * as React from "react";
import { cn } from "@/lib/utils";
import { type FloatingInputProps, FloatingInput } from "./base/floating-input";
import { FloatingLabel } from "./base/floating-label";
 
type FloatingLabelInputProps = FloatingInputProps & {
	containerProps?: React.HTMLAttributes<HTMLDivElement>;
	label?: string;
	error?: boolean;
};
 
const FloatingLabelInput = React.forwardRef<React.ElementRef<typeof FloatingInput>, React.PropsWithoutRef<FloatingLabelInputProps>>(
	({ id, label, error = false, className, containerProps, ...props }, ref) => {
		return (
			<div
				{...containerProps}
				className={cn(
					"relative",
					error && "[&>*]:text-error [&>fieldset]:border-error [&>fieldset]:dark:border-error",
					containerProps?.className,
				)}>
				<FloatingInput
					ref={ref}
					id={id}
					className={cn(className, "focus-visible:ring-ring focus-visible:ring-0 focus-visible:ring-opacity-0", {
						"text-error": error,
					})}
					{...props}
				/>
				<FloatingLabel
					htmlFor={id}
					size={props.size}
					className={cn("peer-focus:text-primary font-medium", {
						"text-error peer-focus:text-error": error,
					})}>
					{label}
				</FloatingLabel>
				<fieldset
					className={cn(
						"absolute peer-focus-visible:border-2 transition-all peer-focus-visible:border-primary inset-0 -top-[5px] border dark:border-input/35 border-input/65 rounded-md m-0 py-0 text-left px-2 pointer-events-none min-w-0 peer-focus-visible:[&>legend]:max-w-full peer-placeholder-shown:[&>legend]:max-w-0",
					)}>
					<legend className="transition-all invisible whitespace-nowrap overflow-hidden w-auto max-w-full h-3 leading-4 text-xs font-normal p-0">
						<span className="px-1 visible inline-block opacity-0">{label}</span>
					</legend>
				</fieldset>
			</div>
		);
	},
);
FloatingLabelInput.displayName = "FloatingLabelInput";
 
export { FloatingLabelInput };

Base label for floating-label-input components.

import * as React from "react";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";
 
// ----------------------------------------------------------------------
 
const floatingLabelVariant = cva(
	"cursor-text text-muted-foreground absolute start-2 z-10 transform duration-300 peer-placeholder-shown:start-2 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 font-medium peer-placeholder-shown:translate-x-1.5 leading-4 text-xs -translate-y-4 top-2 translate-x-1.5 peer-focus:top-2 peer-focus:text-xs peer-focus:-translate-y-4",
	{
		variants: {
			size: {
				sm: "peer-placeholder-shown:text-sm",
				md: "peer-placeholder-shown:text-base",
				lg: "peer-placeholder-shown:text-lg",
			},
		},
		defaultVariants: {
			size: "sm",
		},
	},
);
 
interface FloatingLabelProps extends React.ComponentPropsWithoutRef<typeof Label>, VariantProps<typeof floatingLabelVariant> {}
 
const FloatingLabel = React.forwardRef<React.ElementRef<typeof Label>, FloatingLabelProps>(({ size = "md", className, ...props }, ref) => {
	return <Label className={cn("select-none pointer-events-none", floatingLabelVariant({ size, className }))} ref={ref} {...props} />;
});
FloatingLabel.displayName = "FloatingLabel";
 
export { FloatingLabel, floatingLabelVariant, type FloatingLabelProps };

Base input for floating-label-input components.

import * as React from "react";
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";
 
// ----------------------------------------------------------------------
 
const customInputVariant = cva(
	"px-3 py-2 flex leading-4 w-full text-foreground rounded-md border dark:border-input/35 border-input/65 ring-offset-background bg-transparent file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-0 focus-visible:border-0 disabled:cursor-not-allowed disabled:opacity-35",
	{
		variants: {
			size: {
				sm: "h-12 text-sm",
				md: "h-14 text-base py-4",
				lg: "h-16 text-lg",
			},
		},
		defaultVariants: {
			size: "sm",
		},
	},
);
 
interface FloatingInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">, VariantProps<typeof customInputVariant> {
	size?: "sm" | "md" | "lg" | undefined;
}
 
const CustomInput = React.forwardRef<HTMLInputElement, FloatingInputProps>(({ className, type = "text", size, ...props }, ref) => {
	return <input type={type} className={cn(customInputVariant({ size, className }))} ref={ref} {...props} />;
});
CustomInput.displayName = "CustomInput";
 
const FloatingInput = React.forwardRef<HTMLInputElement, FloatingInputProps>(({ className, ...props }, ref) => {
	return <CustomInput placeholder=" " className={cn("peer bg-transparent border-none", className)} ref={ref} {...props} />;
});
FloatingInput.displayName = "FloatingInput";
 
export { FloatingInput, CustomInput, type FloatingInputProps };

Shadcn

This component is built on top of the excellent foundation provided by Shadcn/UI.

For a deeper dive into the core concepts and building blocks, check out the original Shadcn input component.