Input
A single-line text field for user input.
import { ... } from '@corndev/ui' HTML
<label class="corn-label" for="email">Email</label>
<input type="email" id="email" class="corn-input" data-size="md" placeholder="you@example.com" />
<!-- An icon inside the border, and a button after the text. No JavaScript. -->
<label class="corn-label" for="q">Search</label>
<div class="corn-input-wrapper">
<span class="corn-input-section" data-position="left" aria-hidden="true">⌕</span>
<input id="q" class="corn-input" data-size="md" />
<span class="corn-input-section" data-position="right"><button type="button">Clear</button></span>
</div>
<!-- Text until clicked: editing a name in place. -->
<input class="corn-input" data-variant="unstyled" aria-label="Column name" value="user_id" />
Classes
| Class |
|---|
.corn-input |
.corn-input-wrapper |
.corn-input-section |
.corn-input-reveal |
Attributes
| On | Attribute | Values |
|---|---|---|
.corn-input | data-size | sm|md|lg |
.corn-input | data-error | present or absent |
.corn-input | data-full-width | present or absent |
.corn-input | data-variant | unstyled |
.corn-input-section | data-position | left|right |
Vanilla JS
Required for behaviour — adds the show/hide button for a revealable password field; everything else, sections included, is the markup and classes alone.
import { createInput } from '@corndev/ui';
import '@corndev/ui/theme';
const el = document.querySelector('#target');
const instance = createInput(el, { /* options */ });
Options
| Option | Type | Default | Description |
|---|---|---|---|
size | 'sm'|'md'|'lg' | 'md' | |
disabled | boolean | false | |
error | boolean | false | |
fullWidth | boolean | false | |
variant | 'unstyled' | undefined | unstyled shows no chrome until touched — for a name in a table cell or a heading that becomes editable when clicked. It inherits the surrounding type and keeps its box, so the hit area and the focus ring are unchanged. |
revealable | boolean | false | With type="password", a button inside the field that shows and hides what was typed — for a pasted secret or connection string, where checking it is most of the point. |
revealLabel | string | 'Show password' | Accessible name for that button. It is a toggle (aria-pressed), so the name stays the same. |
React
Input — from @corndev/ui-react.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm'|'md'|'lg' | 'md' | |
disabled | boolean | false | |
error | boolean | false | |
fullWidth | boolean | false | |
variant | 'unstyled' | undefined | unstyled shows no chrome until touched — for a name in a table cell or a heading that becomes editable when clicked. It inherits the surrounding type and keeps its box, so the hit area and the focus ring are unchanged. |
leftSection | React.ReactNode | undefined | Inside the field’s border, before the text — a search icon, a @ prefix. Decorative: it takes no clicks (a click on it focuses the field) and is hidden from screen readers, so put anything it means into the label. |
rightSection | React.ReactNode | undefined | Inside the border, after the text — a clear or “Browse…” button. Interactive, reached by Tab after the field, and given room at whatever width it needs. |
revealable | boolean | false | With type="password", a button inside the field that shows and hides what was typed — for a pasted secret or connection string, where checking it is most of the point. |
revealLabel | string | 'Show password' | Accessible name for that button. It is a toggle (aria-pressed), so the name stays the same. |
type | string | undefined | The input’s type. |
className | string | undefined | On the <input> — or, when a section or revealable adds a wrapper, on the wrapper, which is then the element in your layout. |
style | object | undefined | Same rule as className, so a width lands on whatever draws the field. |
Default
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Ada Lovelace" />Sizes
<Input size="sm" aria-label="Small" />
<Input size="md" aria-label="Medium" />
<Input size="lg" aria-label="Large" />Error state
<Input error aria-label="Email" placeholder="Invalid value" />Disabled
<Input disabled aria-label="Account ID" defaultValue="Can't touch this" />Full width
<Input fullWidth aria-label="Search" placeholder="Stretches to container width" />Icons and buttons inside the field
leftSection puts something before the text, inside the border — a search icon, a
@ prefix. rightSection puts something after it — a clear button, a “Browse…”.
<Input aria-label="Search" leftSection={<IconSearch size={16} />} />
<Input
aria-label="Folder"
rightSection={<Button size="sm" emphasis="quiet">Browse…</Button>}
/>
- The end section is in the flow, so a button of any width gets its own room and the text never runs underneath it. There is no padding or width to supply.
- The start section takes no clicks — a click on the icon focuses the field — and it is hidden from screen readers, so put what it means in the label.
- Tab order is the source order: the field, then an end button.
When there is a section, a .corn-input-wrapper draws the field, and className
and style go on the wrapper — it is the element in your layout, so a width
belongs there. Every other prop, id and aria-* included, stays on the input,
which is what lets a Field label it.
Without React
The layout is CSS alone:
<div class="corn-input-wrapper">
<span class="corn-input-section" data-position="left" aria-hidden="true">⌕</span>
<input class="corn-input" aria-label="Search" />
<span class="corn-input-section" data-position="right"><button type="button">Clear</button></span>
</div>
Editing in place
variant="unstyled" looks like text until someone touches it — a column name in a
table row, a title in a heading:
<h2>
<Input variant="unstyled" aria-label="Collection name" defaultValue="Payments" />
</h2>
It inherits the surrounding size and weight and shows no border or background, but keeps its box: the hit area is still at least 24 × 24, and nothing shifts when it is focused. Focus shows a surface and a ring, since that is the only sign it is an input at all.
Showing a password
revealable on a password field adds a button inside it that shows and hides what
was typed — for a pasted client secret or connection string, where checking it is
most of the point:
<Input type="password" revealable aria-label="Client secret" />
The button is a toggle (aria-pressed) with one steady name, “Show password”,
which revealLabel changes. It is the only part of Input that holds state, so a
revealable field is a client component and every other Input still renders on the
server.