@starasia/input
v4.2.0
Published
input component for starasia UI
Keywords
Readme
@starasia/input
A flexible, design-system-driven input component for React. Implements the Starasia "TextField" Figma spec — three variants × four sizes × eight states × three label positions, plus currency formatting, autocomplete, and a built-in clear button.
Installation
pnpm add @starasia/input
# or
yarn add @starasia/input
# or
npm i @starasia/inputQuick start
import {Input} from "@starasia/input";
function Example() {
const [value, setValue] = useState("");
return (
<Input
label="Email"
required
placeholder="[email protected]"
helperText="We'll never share your email."
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}Props
Layout & sizing
| Prop | Type | Default | Description |
| --------------- | ------------------------------------------------- | -------------- | -------------------------------------------- |
| size | "sm" \| "md" \| "lg" \| "xl" | "md" | Field height & typography scale. |
| variant | "standard" \| "outline" \| "flushed" | "outline" | Visual style of the input box. |
| status | "default" \| "error" \| "success" \| "disable" | "default" | Border color & helper-text styling. |
| labelPosition | "outside-top" \| "outside-left" \| "inside" | "outside-top"| Where the label sits relative to the field. |
| fullWidth | boolean | false | Stretch the component to fill its container. |
Label & helpers
| Prop | Type | Description |
| ------------- | ----------- | ---------------------------------------------------------------------------- |
| label | ReactNode | Field label. |
| required | boolean | Show a red * next to the label. |
| optional | boolean | Show an "Optional" pill next to the label. |
| description | string | Subtle help text rendered above the field. |
| helperText | string | Subtle help text rendered below the field. |
| errorText | string | Red error text rendered below the field — only shown when status="error". |
Adornments
| Prop | Type | Description |
| ------------------- | -------------------------- | -------------------------------------------------------------------------- |
| leftIcon | ReactElement | SVG element placed inside the field on the left. |
| rightIcon | ReactElement | SVG element placed inside the field on the right. |
| onClickLeftIcon | () => void | Click handler for the left icon (icon container becomes clickable). |
| onClickRightIcon | () => void | Click handler for the right icon. |
| leftAddons | ReactNode | Block content attached to the left edge (e.g. "https://"). |
| rightAddons | ReactNode | Block content attached to the right edge (e.g. ".com"). |
Behavior
| Prop | Type | Default | Description |
| ---------------------- | -------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| clearable | boolean | true | Show a × button to clear the value when the field has content. The slot is reserved so the input width stays constant. |
| onClear | () => void | | Fires after the clear button is pressed. |
| currency | boolean | false | Format the value as 1.234.567 while typing. onChange receives the raw numeric string. Caret stays in place when editing in the middle. |
| highlightPlaceholder | string | | Substring of the placeholder rendered in the primary text color (visual emphasis only — placeholder stays inert). |
| options | string[] | | Enables an autocomplete dropdown filtered against the current value. |
| onOptionChange | (value: string) => void | | Fires when the user picks an option from the dropdown. |
All other native <input> HTML attributes (name, type, disabled,
onFocus, onBlur, onKeyDown, value, defaultValue, …) are forwarded to
the underlying element.
Variants
<Input variant="standard" label="Standard" /> {/* filled gray box, no border */}
<Input variant="outline" label="Outline" /> {/* white box, 0.8px border */}
<Input variant="flushed" label="Flushed" /> {/* bottom border only */}
<Input variant="borderless" label="Borderless" /> {/* transparent, no border */}Sizes
sm (32 px) · md (40 px) · lg (48 px) · xl (56 px). Sizes affect typography
and helper-text scale too — sm uses 12 px input text and 10 px helpers; the
others use 14 px input text and 12 px helpers.
Status
<Input status="default" /> {/* subtle border */}
<Input status="error" errorText="Please enter a valid email." />{/* red border + red text */}
<Input status="success" helperText="Email is available." /> {/* green border + ✓ icon */}
<Input status="disable" /> {/* greyed out */}status="success" automatically renders a green checkmark on the right side of
the field.
Label positions
outside-top (default)
<Input
label="Field label"
required
optional
description="Enable multi-factor authentication."
placeholder="[email protected]"
helperText="I am some friendly help text."
/>outside-left
The label and description occupy a column to the left of the field.
<Input
fullWidth
labelPosition="outside-left"
label="Field label"
description="Enable multi-factor authentication."
placeholder="[email protected]"
helperText="I am some friendly help text."
/>inside (floating label)
The label sits inside the field at rest. As soon as the field is focused or has
a value, the label animates upward (font size shrinks, color changes) and the
input field appears below — animation is 180 ms cubic-bezier(0.4, 0, 0.2, 1).
<Input
labelPosition="inside"
label="Field label"
required
rightIcon={<IcMail />}
helperText="Click to start typing."
/>Adornments
<Input leftIcon={<IcSearch />} placeholder="Search…" />
<Input rightIcon={<IcEye />} placeholder="Password" />
<Input
leftIcon={<IcSearch />}
onClickLeftIcon={() => doSomething()}
rightIcon={<IcQrCode />}
onClickRightIcon={() => scanQr()}
/>
<Input leftAddons="https://" placeholder="yourdomain.com" />
<Input rightAddons=".com" placeholder="yourdomain" />
<Input leftAddons="Rp" rightAddons=",-" placeholder="Nominal" />Pass onClickLeftIcon / onClickRightIcon to make an icon clickable. The
component wraps the icon in its own clickable container — your icon's own
onClick is not consumed.
Clear button
The × clear button appears automatically whenever the field has a value and is not disabled. The slot is always reserved, so the input width stays constant whether or not the × is visible — the button just fades in/out.
<Input clearable /> {/* default: true */}
<Input clearable={false} /> {/* opt out */}
<Input onClear={() => track()} />Currency
const [amount, setAmount] = useState("");
<Input
currency
leftAddons="Rp"
placeholder="0"
value={amount}
onChange={(e) => setAmount(e.target.value)} // raw "12345"
/>While typing, the field displays 12.345 (thousands separator). The value
delivered to onChange is the raw numeric string (no separators), so it can be
saved or sent as-is. Editing in the middle of the value preserves the caret
position; backspacing on a thousands separator deletes the digit before it.
Autocomplete
const [value, setValue] = useState("");
<Input
label="Fruit"
placeholder="Type to filter…"
value={value}
onChange={(e) => setValue(e.target.value)}
options={["Apple", "Banana", "Cherry", "Durian"]}
onOptionChange={(picked) => setValue(picked)}
/>Options are filtered by startsWith against the current value and rendered in a
floating dropdown.
Highlight placeholder
<Input
placeholder="Full Name (Required)"
highlightPlaceholder="Required"
/>The matched substring is rendered in the primary text color while the rest of the placeholder stays subtle.
Imperative ref
Input forwards a ref to the underlying <input> element — useful for focus
management, integrating with form libraries, etc.
const ref = useRef<HTMLInputElement>(null);
<Input ref={ref} />
// later
ref.current?.focus();Design tokens
The component reads its colors from CSS custom properties prefixed with
--sa-input-*. The defaults match the Starasia Figma library; override them on
your :root (or any ancestor) to retheme:
| Token | Default | Used for |
| ------------------------------- | ------------------------ | ------------------------------- |
| --sa-input-text-primary | #292a2e | Input value & label |
| --sa-input-text-subtle | #505258 | Standard placeholder |
| --sa-input-text-subtlest | #6b6e76 | Helper / description / outline placeholder |
| --sa-input-text-error | #a4133c | Error text |
| --sa-input-border-subtle | rgba(11,18,14,0.14) | Default border |
| --sa-input-border-brand | #1976d2 | Focus border |
| --sa-input-border-brand-subtle| #90caf9 | Focus ring (inside-label) |
| --sa-input-border-danger | #c9184a | Error border |
| --sa-input-border-danger-subtle| #ffb3c1 | Error focus ring |
| --sa-input-border-success | #28ac6e | Success border |
| --sa-input-border-disabled | rgba(24,26,25,0.56) | Disabled border |
| --sa-input-bg-neutral | #f0f1f2 | Standard variant background |
| --sa-input-bg-error-subtlest | #fff0f3 | Standard + error background |
| --sa-input-bg-success-subtlest| #ecfff6 | Standard + success background |
TypeScript
All public types are exported alongside the component:
import type {
IInputProps,
InputSize,
InputVariant,
InputStatus,
InputLabelPosition,
} from "@starasia/input";License
ISC
