Input

A text input component with support for labels, descriptions, and error states.

import { Input } from "~/components/input";

export default function InputBasicDemo() {
  return <Input label="Username" placeholder="Enter username" />;
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add input

Manual

Create the recipe file at src/components/recipes/input.ts:

import { tv, type VariantProps } from "tailwind-variants";

export const inputVariants = tv({
  slots: {
    root: "grid gap-1.5",
    input:
      "h-8 rounded-md border border-input bg-background px-2.5 py-1.5 text-base md:text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50",
    description: "text-sm text-muted-foreground",
    error: "text-sm text-destructive",
  },
  variants: {
    error: {
      true: {
        input: "border-destructive focus-visible:ring-destructive",
      },
    },
  },
  defaultVariants: {
    error: false,
  },
});

export type InputVariants = VariantProps<typeof inputVariants>;

Create the component directory and files.

src/components/input/input.base.tsx:

import { Field as ArkField } from "@ark-ui/solid/field";
import { splitProps, type Component } from "solid-js";
import { inputVariants, labelVariants, type InputVariants } from "../recipes/input";

const styles = inputVariants();

const InputRoot: Component<ArkField.RootProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkField.Root class={styles.root({ class: local.class })} {...others} />;
};

const InputLabel: Component<ArkField.LabelProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkField.Label class={labelVariants({ class: local.class })} {...others} />;
};

const InputField: Component<ArkField.InputProps & InputVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "error"]);
  return (
    <ArkField.Input class={styles.input({ class: local.class, error: local.error })} {...others} />
  );
};

const InputDescription: Component<ArkField.HelperTextProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkField.HelperText class={styles.description({ class: local.class })} {...others} />;
};

const InputErrorText: Component<ArkField.ErrorTextProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkField.ErrorText class={styles.error({ class: local.class })} {...others} />;
};

export const Input = {
  Root: InputRoot,
  Label: InputLabel,
  Field: InputField,
  Description: InputDescription,
  ErrorText: InputErrorText,
};

src/components/input/index.tsx:

import { Field as ArkField } from "@ark-ui/solid/field";
import { splitProps, type Component } from "solid-js";
import { Input as InputBase } from "./input.base";
import { InputVariants } from "../recipes/input";

type InputProps = {
  label?: string;
  description?: string;
  error?: string;
} & ArkField.InputProps &
  Omit<InputVariants, "error">;

const Input: Component<InputProps> = (props) => {
  const [local, others] = splitProps(props, ["class", "label", "description", "error", "children"]);
  return (
    <InputBase.Root class={local.class} invalid={!!local.error}>
      {local.label && <InputBase.Label>{local.label}</InputBase.Label>}
      <InputBase.Field error={!!local.error} {...others} />
      {local.description && !local.error && (
        <InputBase.Description>{local.description}</InputBase.Description>
      )}
      {local.error && <InputBase.ErrorText>{local.error}</InputBase.ErrorText>}
    </InputBase.Root>
  );
};

export { Input, InputBase };

export { inputVariants, type InputVariants } from "../recipes/input";

Note: Make sure your project has the Tailwind CSS theme variables set up (--background, --foreground, --ring, --border, etc.) or override the utility classes to match your design system.

Usage

Basic Usage

A basic text input field.

import { Input } from "~/components/input";

export default function InputBasicDemo() {
  return <Input label="Username" placeholder="Enter username" />;
}

With Description

Add a helper description below the input using the description prop.

We'll never share your email.
import { Input } from "~/components/input";

export default function InputDescriptionDemo() {
  return (
    <Input
      label="Email"
      description="We'll never share your email."
      placeholder="email@example.com"
    />
  );
}

With Error State

Display an error message below the input using the error prop.

Invalid email address
import { Input } from "~/components/input";

export default function InputErrorDemo() {
  return <Input label="Email" error="Invalid email address" type="email" />;
}

API Reference

PropTypeDefault
labelstring
descriptionstring
errorstring
classstring
(HTML input attrs)