Siteplane UI

Input

Enter a single line of text or a native input value.

Markdown
"use client";

import { Field, FieldLabel } from "@/siteplane/components/field";
import { Input } from "@/siteplane/components/input";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Project name</FieldLabel>
      <Input />
    </Field>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/input

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/action-icons @siteplane/button @siteplane/input-group

Usage

import { Input } from "@/siteplane/components/input";

<Input aria-label="Description" />;

Variants

Choose the control surface without changing its behavior. elevated lifts the field with the hairline and soft shadow of an elevated Card, so it reads on a white surface too. soft replaces the border with a subtle fill.

"use client";

import { Field, FieldLabel } from "@/siteplane/components/field";
import { Input } from "@/siteplane/components/input";

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      {(["default", "elevated", "inset", "soft"] as const).map((variant) => (
        <Field key={variant}>
          <FieldLabel>Project name, {variant}</FieldLabel>
          <Input variant={variant} placeholder="Website redesign" />
        </Field>
      ))}
    </div>
  );
}

Sizes

Omit size to inherit the provider's control size. The text is 14px, or 16px at xl; textSize sets it at any size. Touch screens always use 16px text.

"use client";

import { Field, FieldLabel } from "@/siteplane/components/field";
import { Input } from "@/siteplane/components/input";

const sizes = [
  { size: "sm", label: "small" },
  { size: "md", label: "medium" },
  { size: "lg", label: "large" },
  { size: "xl", label: "extra large" },
] as const;

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      {sizes.map(({ size, label }) => (
        <Field key={size}>
          <FieldLabel>Project name, {label}</FieldLabel>
          <Input size={size} defaultValue="Website redesign" />
        </Field>
      ))}
    </div>
  );
}

Disabled, read-only and invalid

disabled blocks editing; readOnly keeps the value focusable and submitted. Set invalid on Field and explain the problem with FieldError.

Enter a complete email address.
"use client";

import { Field, FieldError, FieldLabel } from "@/siteplane/components/field";
import { Input } from "@/siteplane/components/input";

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      <Field disabled>
        <FieldLabel>Billing email</FieldLabel>
        <Input type="email" defaultValue="finance@example.com" />
      </Field>
      <Field>
        <FieldLabel>Project ID</FieldLabel>
        <Input readOnly defaultValue="PRJ-2048" />
      </Field>
      <Field invalid>
        <FieldLabel>Email</FieldLabel>
        <Input type="email" defaultValue="mara@example" />
        <FieldError match>Enter a complete email address.</FieldError>
      </Field>
    </div>
  );
}

File input

type="file" keeps the native file picker. Its button and file name sit on the center of the field, with the height and start padding of any other input at each size. Uploading the selected file is up to your application.

PDF or image, up to 10 MB.

"use client";

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";
import { Input } from "@/siteplane/components/input";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Attachment</FieldLabel>
      <Input type="file" accept=".pdf,image/*" />
      <FieldDescription>PDF or image, up to 10 MB.</FieldDescription>
    </Field>
  );
}

Password

type="password" masks the value as larger, evenly spaced dots at the field’s own text size. They come from a font with a bigger bullet, Verdana or DejaVu Sans on Linux; on a system with neither, such as Android, the dots keep the text font’s size and only the spacing opens up. The placeholder and the revealed text keep the normal text style.

To let people show or hide the value, put the input in an InputGroup with an icon Button, as below. Password visibility describes the pattern.

"use client";

import { RevealIcon } from "@/siteplane/components/action-icons";
import { Button } from "@/siteplane/components/button";
import { Field, FieldLabel } from "@/siteplane/components/field";
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/siteplane/components/input-group";
import { useState } from "react";

export default function Example() {
  const [visible, setVisible] = useState(false);

  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Password</FieldLabel>
      <InputGroup>
        <InputGroupInput
          autoComplete="current-password"
          defaultValue="correct horse battery"
          type={visible ? "text" : "password"}
        />
        <InputGroupAddon align="inline-end">
          <Button
            aria-label="Show password"
            aria-pressed={visible}
            profile="field-action"
            shape="square"
            onClick={() => setVisible((current) => !current)}
          >
            <RevealIcon revealed={visible} />
          </Button>
        </InputGroupAddon>
      </InputGroup>
    </Field>
  );
}

Controlled value

Pass value and onValueChange to own the text, for example to show how many characters remain. maxLength limits entry natively.

32 characters left

"use client";

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";
import { Input } from "@/siteplane/components/input";
import { useState } from "react";

const maxLength = 32;

export default function Example() {
  const [value, setValue] = useState("");

  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Display name</FieldLabel>
      <Input maxLength={maxLength} value={value} onValueChange={setValue} />
      <FieldDescription>
        <span className="tabular-nums">{maxLength - value.length}</span>{" "}
        characters left
      </FieldDescription>
    </Field>
  );
}

Floating label

Put the Input in a Field with labelPlacement="floating" and its label sits inside the field until it has focus or a value; Field lists the controls that support it. Floating fields take no leading icon, so an Input with insetStart keeps its label above it.

We send the receipt here.

"use client";

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";
import { Input } from "@/siteplane/components/input";

export default function Example() {
  return (
    <Field className="w-full max-w-sm" labelPlacement="floating">
      <FieldLabel>Email</FieldLabel>
      <Input autoComplete="email" type="email" />
      <FieldDescription>We send the receipt here.</FieldDescription>
    </Field>
  );
}

API reference

Component properties
PropTypeDefaultDescription
sizesm | md | lg | xl | numberprovider / lgVisual control size. A number sets native input size instead.
textSizesm | baseunsetText size at any control size: sm is 14px, base is 16px. Unset uses 14px, or 16px at xl; touch screens use 16px.
variantdefault | elevated | inset | softdefaultSurface treatment.
unstyledbooleanfalseRemoves the outer surface recipe; preserves control anatomy.
classNamestringunsetStyles the outer span, not the native control.
nativeInputbooleanfalseRenders a native input element with the same Field wiring as the default input.
renderInputReactElement<input props>unsetRenders the supplied input element with the input classes; takes precedence over nativeInput. Inside a Field it is connected like the default input.
hidePickerIndicatorbooleanfalseHides the native picker affordance; supply another understandable interaction when needed.
insetStart / insetEndbooleanfalseMarks an edge as inset for a composed adjoining element.
onValueChange(value, eventDetails) => voidunsetText-change callback; native onChange also fires.

InputProps and InputPrimitive are exported. style reaches the actual input, including callback styles. renderInput preserves its own className alongside the input classes; props set on the supplied element take precedence over Input props.

Inside a Field, nativeInput and renderInput are wired like the default input: FieldLabel labels the rendered input, FieldDescription and FieldError describe it, and Field state such as invalid and disabled reaches it.

Other input, form and validation props follow Base UI Input.

Read the installed source.

Usage notes

Always provide a visible label or an accessible name. Placeholder text is not a label. Use Field for related help and errors. type="search" hides the browser's built-in clear button, type="date" keeps the native picker (hidePickerIndicator hides its icon), and type="hidden" renders only the native input. A controlled input does not reset with its form; reset your state in onReset. The control does not persist data or implement request loading. A plain native form and the kit Form have different validation presentation; see Forms.