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

OnAttributeValues
.corn-inputdata-sizesm|md|lg
.corn-inputdata-errorpresent or absent
.corn-inputdata-full-widthpresent or absent
.corn-inputdata-variantunstyled
.corn-input-sectiondata-positionleft|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

OptionTypeDefaultDescription
size'sm'|'md'|'lg''md'
disabledbooleanfalse
errorbooleanfalse
fullWidthbooleanfalse
variant'unstyled'undefinedunstyled 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.
revealablebooleanfalseWith 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.
revealLabelstring'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

PropTypeDefaultDescription
size'sm'|'md'|'lg''md'
disabledbooleanfalse
errorbooleanfalse
fullWidthbooleanfalse
variant'unstyled'undefinedunstyled 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.
leftSectionReact.ReactNodeundefinedInside 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.
rightSectionReact.ReactNodeundefinedInside 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.
revealablebooleanfalseWith 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.
revealLabelstring'Show password'Accessible name for that button. It is a toggle (aria-pressed), so the name stays the same.
typestringundefinedThe input’s type.
classNamestringundefinedOn the <input> — or, when a section or revealable adds a wrapper, on the wrapper, which is then the element in your layout.
styleobjectundefinedSame 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.