All pages

Components

Input

Two text fields, split the same way the buttons are. Input is the shadcn primitive on Curation's tokens — bordered, h-9, ring focus — for inline forms. InputBox is the ported legacy pill: a 3.13rem filled row with slots inside the fill.

Inline

A plain <input> on the semantic tokens: border-input hairline, transparent ground, the three-pixel ring-ring/50 focus, and aria-invalid flipping the border and ring to destructive.

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

<Input placeholder="Search your lists" />
<Input aria-invalid placeholder="Something went wrong here" />

Input box

The ported InputBox.vue: a filled bg-ink/[0.03] row, rounded-lg (legacy's 1rem, snapped to the ladder), semibold text with the placeholder one ink rung down. It closes the login screen and the home composer — the fields that are the screen, rather than a row in a form.

import { InputBox } from "@/components/base/input-box";

<InputBox placeholder="Email address" />

Adornments

left is the glyph that says what the field is before anything is typed in it; right is the slot the login screen puts its “send code” countdown in (CodeButton, in app/[locale]/login/login-shell.tsx — text rather than a pill, because the primary action is the button below the field). Both sit inside the fill and share the row's gap, so neither needs to know the field's padding.

<InputBox
left={<SearchIcon className="size-4 text-ink-tertiary" />}
right={<CodeButton state="ready" onClick={send}>Send code</CodeButton>}
placeholder="Verification code"
/>