Canvas Design System
Main Site Tokens

Input

Collects a value from the user, with the label, hint, and error wiring already correct.

When to Use

Use when: You need any free-text or numeric field in a form. It covers text, email, password, number, tel, url, search, textarea, and select through one call, so the label and validation markup stay consistent.
Don't use when: The choice is boolean — use Toggle. For a richer dropdown use Select, and for multi-value entry use Chip Input.
warning Corrected July 2026. This page previously demonstrated icon, iconPosition, state, and helper options. The template reads none of them — code copied from those examples silently did nothing. The real option for help text is hint, and the error state is inferred from error being non-empty. See Options below.

Variants

Basic

The label is bound to the field by id, which defaults to the field name. Always pass a label.

<?= ws_input('workshop_name', [
    'label'       => 'Workshop name',
    'placeholder' => 'Enter workshop name…',
    'required'    => true,
]) ?>

Types

One helper covers nine types. textarea and select render different elements but keep the same label, hint, and error treatment.

<?= ws_input('email', ['type' => 'email', 'label' => 'Email address']) ?>
<?= ws_input('description', ['type' => 'textarea', 'label' => 'Description', 'rows' => 4]) ?>

<!-- NOTE the options shape — list of arrays, not value => label pairs -->
<?= ws_input('category', [
    'type'        => 'select',
    'label'       => 'Category',
    'placeholder' => 'Select category…',
    'options'     => [
        ['value' => 'ideation', 'label' => 'Ideation'],
        ['value' => 'strategy', 'label' => 'Strategy'],
    ],
]) ?>
warning Options shape differs from Select. ws_input(type: 'select') wants a list of ['value' => …, 'label' => …, 'disabled' => …] arrays. ws_select() wants flat value => label pairs. Passing the flat shape here throws Cannot access offset of type string on string — a fatal, not a silent miss. This page previously shipped that exact bug. When you want a dropdown, prefer ws_select().

Sizes

<?= ws_input('small', ['label' => 'Small', 'size' => 'sm']) ?>

States

There is no state option. The error state is derived: pass a non-empty error and the field styles itself, sets aria-invalid, and points aria-describedby at the message.

We only use this for session reminders.

StateBehavior
DefaultBordered field using the --input-* token family.
HoverBorder shifts to --input-border-hover.
FocusBorder becomes --input-border-focus with a --input-ring halo. This is the sanctioned non-red focus accent — don't remove it.
ErrorTriggered by a non-empty error. Adds .ws-input--error and .ws-field--error, sets aria-invalid="true", and renders the message with role="alert".
HintRendered only when there's no error — the error replaces the hint rather than stacking below it.
DisabledNative disabled: greyed, unfocusable, and not submitted with the form.
Read-onlyNative readonly: still focusable and still submitted, unlike disabled. Use it for values the user should see and copy but not change.
Success.ws-input--success exists in CSS but nothing in the template emits it. Apply it yourself through class if you want a verified-field treatment.
<?= ws_input('email', [
    'label' => 'Email address',
    'error' => $errors['email'] ?? null,   // non-empty ⇒ error state
    'hint'  => 'We only use this for session reminders.',
]) ?>

Real-World Usage

A save-workshop form on redisplay after failed validation: values echoed back, per-field errors from the validation array, and autocomplete hints set so browsers fill sensibly.

One sentence on what the group should leave with.

<?= ws_input('title', [
    'label'     => 'Workshop title',
    'value'     => $plan['title'] ?? '',
    'error'     => $errors['title'] ?? null,
    'required'  => true,
    'maxlength' => 120,
]) ?>

<?= ws_input('goal', [
    'type'  => 'textarea',
    'label' => 'Goal',
    'value' => $plan['goal'] ?? '',
    'error' => $errors['goal'] ?? null,
    'hint'  => 'One sentence on what the group should leave with.',
    'rows'  => 3,
]) ?>

Options

OptionTypeDefaultPurpose
$namestring''Positional. The field name, and the default id.
typestring'text'text | email | password | number | tel | url | search | date | time | color | file | textarea | select. color gets a full-height swatch so it matches a text field; file draws its picker button as a secondary button.
idstring$nameElement ID, used to bind the label
labelstringnullVisible label. Effectively required — see Accessibility.
placeholderstring''Placeholder text. Never a substitute for a label.
valuestring''Current value
errorstringnullError message. A non-empty value is the error state.
hintstringnullHelp text below the field. Hidden while an error is showing.
requiredboolfalseNative required attribute. Draws no asterisk: Workshopr defaults to required (DESIGN.md §11.3)
optionalboolfalseAppends "(optional)" to the label: the way an optional field is marked
disabledboolfalseDisabled and excluded from submission
readonlyboolfalseNot editable, but focusable and submitted
autocompletestringnullAutocomplete token — set it on name, email, and address fields
minlength / maxlengthintnullNative length constraints
patternstringnullNative validation pattern
optionsarray[]select type only. A list of arrays: [['value' => …, 'label' => …, 'disabled' => false]]. Not the same shape as ws_select().
rowsint4textarea type only
showPasswordTogglebooltruepassword type only — the visibility toggle button
sizestring'md'sm | md | lg
classstring''Additional CSS classes
attrsarray[]Extra HTML attributes as key/value pairs
info Not supported: icon, iconPosition, state, helper. The CSS carries .ws-input-wrap__icon--left/right rules, but no template emits them — leading icons would need markup you write yourself, or a template change.

Accessibility

ConcernBehavior
LabellingA real <label for> bound to the field's id. Omitting label leaves the field unnamed — a placeholder does not substitute, since it vanishes on typing.
ErrorsThe message carries role="alert", so it's announced when it appears, and the field gets aria-invalid="true" plus aria-describedby pointing at it.
HintsWired through aria-describedby to the hint's ID — but only when there's no error. Error takes the slot; the two never both describe the field.
Password toggleA real button labelled aria-label="Toggle password visibility", with both eye icons marked aria-hidden. It swaps the input's type via an inline handler.
KeyboardAll native. Disabled fields leave the tab order; read-only fields stay in it.
Required and optionalrequired sets the native attribute, so the state is announced and the browser checks it; there is no asterisk. optional puts "(optional)" inside the label, so it is part of the field's name for everyone.
AutofillPass autocomplete on personal-data fields. It's an accessibility feature (WCAG 1.3.5), not just convenience.

Tokens

TokenUsed for
--input-bg / --input-text / --input-radiusField surface, text, and corners
--input-border / --input-border-hover / --input-border-focusBorder across resting, hover, and focus
--input-ring / --input-ring-widthFocus halo
--focus-ring-width / --focus-ring-offsetShared focus geometry
--input-placeholder / --gray-400Placeholder text
--gray-50Disabled fill
--color-error / --color-error-lightError border and message
--color-success / --color-success-lightThe unused success treatment
--brand / --brand-ink / --brand-tintAccent
--text-body / --text-ui / --text-metaField, label, and hint type scale
--font-bodyBody face
--transition-interactiveBorder and ring transitions

CSS Classes

ClassPurpose
.ws-inputThe field element
.ws-input--sm / --lgSize modifiers (md emits nothing)
.ws-input--errorError styling, emitted when error is set
.ws-input--successSuccess styling — defined in CSS, never emitted by the template
.ws-field--errorError class on the wrapper, for styling the label and message together
.ws-input-wrapWrapper for a field with adornments
.ws-input-wrap--has-icon-left / --has-icon-rightPadding adjustments for icons — orphaned, since no template emits them
.ws-input-wrap__icon / --left / --rightIcon positioning — also orphaned

Files

FilePurpose
includes/components/helpers.phpws_input() helper function
includes/components/input.phpTemplate — type branching, ARIA wiring, password toggle
includes/components/components.cssStyles (.ws-input rules)