Pin Input
A pin input component for entering one-time codes, PINs, and numeric sequences with individual input fields.
import { PinInput, PinInputControl, PinInputInput, PinInputLabel } from "~/components/pin-input";
import { Index } from "solid-js";
export default function PinInputBasicDemo() {
return (
<PinInput>
<PinInputLabel>Code</PinInputLabel>
<PinInputControl>
<Index each={[0, 1, 2, 3]}>{(id) => <PinInputInput index={id()} />}</Index>
</PinInputControl>
</PinInput>
);
}Installation
CLI
Run the following command to add the component to your project:
npx @ark-preset/cli@latest add pin-inputManual
Create the recipe file at src/components/recipes/pin-input.ts:
import { tv, type VariantProps } from "tailwind-variants";
export const pinInputVariants = tv({
slots: {
root: "flex flex-col gap-1.5",
control: "inline-flex gap-2",
input:
"size-8 text-center text-base font-medium rounded-md border border-input bg-background outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:opacity-50 disabled:cursor-not-allowed data-[invalid]:border-destructive data-[invalid]:focus-visible:ring-destructive placeholder:text-muted-foreground",
},
});
export type PinInputVariants = VariantProps<typeof pinInputVariants>;Create the component directory and files.
src/components/pin-input/pin-input.base.tsx:
import { PinInput as ArkPinInput } from "@ark-ui/solid/pin-input";
import { splitProps, type Component } from "solid-js";
import { pinInputVariants, labelVariants } from "../recipes/pin-input";
const styles = pinInputVariants();
const Root: Component<ArkPinInput.RootProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPinInput.Root class={styles.root({ class: local.class })} {...others} />;
};
const RootProvider: Component<ArkPinInput.RootProviderProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPinInput.RootProvider class={styles.root({ class: local.class })} {...others} />;
};
const Control: Component<ArkPinInput.ControlProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPinInput.Control class={styles.control({ class: local.class })} {...others} />;
};
const Input: Component<ArkPinInput.InputProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPinInput.Input class={styles.input({ class: local.class })} {...others} />;
};
const Label: Component<ArkPinInput.LabelProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPinInput.Label class={labelVariants({ class: local.class })} {...others} />;
};
const HiddenInput = ArkPinInput.HiddenInput;
export const PinInput = { Root, RootProvider, Control, Input, Label, HiddenInput };src/components/pin-input/index.tsx:
import { PinInput as PinInputBase } from "./pin-input.base";
const PinInput = PinInputBase.Root;
const PinInputRootProvider = PinInputBase.RootProvider;
const PinInputControl = PinInputBase.Control;
const PinInputInput = PinInputBase.Input;
const PinInputLabel = PinInputBase.Label;
export {
PinInput,
PinInputRootProvider,
PinInputControl,
PinInputInput,
PinInputLabel,
PinInputBase,
};
export { pinInputVariants, type PinInputVariants } from "../recipes/pin-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 multi-input field for PIN and verification codes.
import { PinInput, PinInputControl, PinInputInput, PinInputLabel } from "~/components/pin-input";
import { Index } from "solid-js";
export default function PinInputBasicDemo() {
return (
<PinInput>
<PinInputLabel>Code</PinInputLabel>
<PinInputControl>
<Index each={[0, 1, 2, 3]}>{(id) => <PinInputInput index={id()} />}</Index>
</PinInputControl>
</PinInput>
);
}Masked
Use the mask prop to obscure sensitive input values.
import { PinInput, PinInputControl, PinInputInput, PinInputLabel } from "~/components/pin-input";
import { Index } from "solid-js";
export default function PinInputMaskedDemo() {
return (
<PinInput placeholder="•" mask>
<PinInputLabel>PIN</PinInputLabel>
<PinInputControl>
<Index each={[0, 1, 2, 3]}>{(id) => <PinInputInput index={id()} />}</Index>
</PinInputControl>
</PinInput>
);
}Advanced Usage
Root Provider
Use PinInputRootProvider when you need to control the pin input state externally.
import { usePinInput } from "@ark-ui/solid/pin-input";
import {
PinInputRootProvider,
PinInputControl,
PinInputInput,
PinInputLabel,
} from "~/components/pin-input";
import { Index } from "solid-js";
export default function PinInputRootProviderDemo() {
const pinInput = usePinInput();
return (
<div class="space-y-4">
<output class="block text-sm text-muted-foreground">
Value: {JSON.stringify(pinInput().value)}
</output>
<PinInputRootProvider value={pinInput}>
<PinInputLabel>Code</PinInputLabel>
<PinInputControl>
<Index each={[0, 1, 2, 3]}>{(id) => <PinInputInput index={id()} />}</Index>
</PinInputControl>
</PinInputRootProvider>
</div>
);
}The key difference:
PinInput(Root) — manages its own state internally. Use for simple, self-contained pin inputs.PinInputRootProvider— accepts a pre-created pin input context viausePinInput. Use when you need to read or control the pin input state from outside the component tree.
API Reference
See the Ark UI Pin Input documentation.