Input
A sunken text field for text, passwords and numbers.
Installation
npx shadcn@latest add @runqora/craft-inputUsage
import { Input } from "@/components/craft/input";
<label htmlFor="name">World name</label>
<Input id="name" placeholder="World name" />;Examples
States and sizes
Icons
With React Hook Form and Zod
Input forwards its ref to the real input, so register works directly. Submit
this form empty to see its error.
Props
Prop
Type
Input also accepts every attribute of an <input>. They go to the input
element, while className goes to the field around it.
Accessibility
- Give every input a visible
<label>, or anaria-labelwhen the context already makes its purpose clear. errorsetsaria-invalid. Link your message to the input witharia-describedby, as the form example does.- A red outline isn't the only sign of an error: always show a message too.
- Icons are decorative. A button placed inside the field needs its own label.
Customization
Change the color of one field's outline by overriding its token.
<Input className="[--craft-outline:#2e6be5]" />Dependencies
@base-ui/react- shadcn's
utils, andcraft-theme
Source
"use client";
import { Input as BaseInput } from "@base-ui/react/input";
import type { ComponentProps, ReactNode } from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends Omit<ComponentProps<typeof BaseInput>, "size"> {
/** Marks the value as invalid: a red outline and `aria-invalid`. */
error?: boolean;
/** Rendered inside the field, before the text. */
leadingIcon?: ReactNode;
/** The height of the field. */
size?: "sm" | "md" | "lg";
/** Rendered inside the field, after the text. */
trailingIcon?: ReactNode;
}
const sizes = {
lg: "h-(--craft-control-lg) text-2xl",
md: "h-(--craft-control-md) text-xl",
sm: "h-(--craft-control-sm) text-lg",
};
export function Input({
className,
error,
leadingIcon,
size = "md",
trailingIcon,
...props
}: InputProps) {
return (
<span
className={cn(
"craft-inset font-craft-display text-craft-text flex w-full items-center gap-2 px-3",
"focus-within:craft-focus [&_svg]:size-4 [&_svg]:shrink-0",
"has-aria-invalid:[--craft-outline:var(--craft-destructive)]",
"has-disabled:text-craft-placeholder has-disabled:pointer-events-none has-disabled:[--craft-outline:var(--craft-disabled-outline)]",
sizes[size],
className
)}
data-slot="input"
>
{leadingIcon}
<BaseInput
aria-invalid={error || undefined}
className="placeholder:text-craft-placeholder h-full w-full min-w-0 flex-1 [appearance:textfield] bg-transparent outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
{...props}
/>
{trailingIcon}
</span>
);
}