Skip to main content

Inputs

InputField, Textarea, Slider, and ColorInput — text, multiline, range, and color.

Quick start
import { InputField, Textarea, Slider, ColorInput } from "mates-ui";
import { atom } from "mates";

const email = atom("");
InputField({ label: "Email address", type: "email", leadingIcon: "mail", atom: email });
Slider({ label: "Budget", value: 2500, min: 0, max: 10000, step: 100,
         showValue: true, format: v => "$" + v.toLocaleString() });

Create account — outlined & filled

import { html, atom, renderApp } from 'mates';
import { InputField } from 'mates-ui';

const App = () => {
const firstName = atom("");
const lastName = atom("");
const email = atom("");
const password = atom("");

return () => html`<x-col gap="12px">
${InputField({ label: "First name", atom: firstName, id: "fn" })}
${InputField({ label: "Last name", atom: lastName, id: "ln" })}
${InputField({ label: "Email", atom: email, id: "em", type: "email", leadingIcon: "mail" })}
${InputField({ label: "Password", atom: password, id: "pw", type: "password", leadingIcon: "lock" })}
</x-col>`;
};

renderApp(App, document.getElementById('app'));

InputField — sizes sm / md / lg

import { html, renderApp } from 'mates';
import { InputField } from 'mates-ui';

const App = () => () => html`<x-col gap="20px">
<x-col gap="8px">
${InputField({ size: "sm", label: "Email", type: "email", placeholder: "jane@example.com", leadingIcon: "mail" })}
${InputField({ size: "sm", label: "Password", type: "password", leadingIcon: "lock" })}
</x-col>
<x-col gap="8px">
${InputField({ size: "md", label: "Email", type: "email", placeholder: "jane@example.com", leadingIcon: "mail" })}
${InputField({ size: "md", label: "Password", type: "password", leadingIcon: "lock" })}
</x-col>
<x-col gap="8px">
${InputField({ size: "lg", label: "Email", type: "email", placeholder: "jane@example.com", leadingIcon: "mail" })}
${InputField({ size: "lg", label: "Password", type: "password", leadingIcon: "lock" })}
</x-col>
</x-col>`;

renderApp(App, document.getElementById('app'));

InputField — states

import { html, atom, renderApp } from 'mates';
import { InputField } from 'mates-ui';

const App = () => {
const coupon = atom("");

return () => html`<x-col gap="16px">
${InputField({ label: "Coupon code", placeholder: "SAVE20", atom: coupon, id: "cp", leadingIcon: "redeem", helperText: "Enter a valid promo code at checkout." })}
${InputField({ label: "Email address", type: "email", leadingIcon: "mail", value: "not-an-email", error: true, errorText: "Please enter a valid email address." })}
${InputField({ label: "Subscription plan", value: "Pro — $29 / month", leadingIcon: "workspace_premium", disabled: true, helperText: "Manage your plan in Billing settings." })}
${InputField({ label: "Account ID", value: "acc_1NxUkP2eZvKYlo2C", leadingIcon: "tag", readonly: true, helperText: "Use this ID when contacting support." })}
${InputField({ label: "Verifying email…", type: "email", value: "jane@example.com", leadingIcon: "mail", trailingIcon: "progress_activity", helperText: "Checking if this email is available…" })}
</x-col>`;
};

renderApp(App, document.getElementById('app'));

InputField — icons, prefix & suffix

import { html, atom, renderApp } from 'mates';
import { InputField } from 'mates-ui';

const App = () => {
const search = atom("");
const website = atom("sarah.dev");

return () => html`<x-col gap="16px">
${InputField({ label: "Search orders", leadingIcon: "search", type: "search", placeholder: "Order # or customer name…", atom: search, id: "srch" })}
${InputField({ label: "New password", type: "password", leadingIcon: "lock", trailingIcon: "visibility", helperText: "Click the eye icon to reveal." })}
${InputField({ label: "Invoice amount", type: "number", prefix: "$", suffix: "USD", placeholder: "0.00", helperText: "Excluding tax." })}
${InputField({ label: "Website", type: "url", leadingIcon: "language", prefix: "https://", atom: website, id: "web", placeholder: "yourdomain.com" })}
${InputField({ label: "Twitter / X handle", leadingIcon: "alternate_email", prefix: "@", placeholder: "yourhandle", helperText: "Without the @ sign." })}
</x-col>`;
};

renderApp(App, document.getElementById('app'));

Textarea — bio, notes, support

import { html, atom, renderApp } from 'mates';
import { Textarea } from 'mates-ui';

const App = () => {
const bio = atom("Building the future, one commit at a time.");

return () => html`<x-col gap="12px">
${Textarea({ label: "Bio", atom: bio, id: "bio", rows: 3, helperText: "Tell us about yourself", maxLength: 200, showCount: true })}
${Textarea({ label: "Filled variant", id: "bio2", variant: "filled", rows: 2, placeholder: "Start typing..." })}
${Textarea({ label: "Error state", id: "bio3", error: true, errorText: "This field is required", rows: 2 })}
</x-col>`;
};

renderApp(App, document.getElementById('app'));

Slider — budget, font size, volume

import { html, atom, renderApp } from 'mates';
import { Slider } from 'mates-ui';

const App = () => {
const budget = atom(2500);
const fontSize = atom(16);

return () => html`<x-col gap="24px">
${Slider({ label: "Monthly budget", value: budget(), min: 0, max: 10000, step: 100, format: v => '$' + v.toLocaleString(), onInput: (v) => budget.set(v) })}
${Slider({ label: "Editor font size", value: fontSize(), min: 10, max: 28, step: 1, format: v => v + 'px', onInput: (v) => fontSize.set(v) })}
${Slider({ label: "Image quality", value: 80, min: 10, max: 100, step: 10, format: v => v + '%' })}
${Slider({ label: "Archived (read-only)", value: 60, disabled: true })}
</x-col>`;
};

renderApp(App, document.getElementById('app'));

ColorInput — brand palette, sizes & states

import { html, atom, renderApp } from 'mates';
import { ColorInput } from 'mates-ui';

const App = () => {
const accent = atom("#2563eb");
const brand = atom("#7c3aed");
const bg = atom("#f8fafc");

return () => html`<x-col gap="20px">
<x-row gap="16px">
${ColorInput({ label: "Accent", value: accent(), onChange: (v) => accent.set(v.toLowerCase()) })}
${ColorInput({ label: "Brand purple", value: brand(), onChange: (v) => brand.set(v.toLowerCase()) })}
${ColorInput({ label: "Background", value: bg(), onChange: (v) => bg.set(v.toLowerCase()) })}
</x-row>
<x-row gap="16px">
${ColorInput({ size: "sm", label: "sm", value: "#0d9488" })}
${ColorInput({ size: "md", label: "md", value: "#7c3aed" })}
${ColorInput({ size: "lg", label: "lg", value: "#ea580c" })}
</x-row>
<x-row gap="16px">
${ColorInput({ label: "Disabled", value: "#94a3b8", disabled: true })}
${ColorInput({ label: "Error", value: "#ef4444", error: true })}
${ColorInput({ value: "#22c55e", ariaLabel: "Pick a theme colour" })}
</x-row>
</x-col>`;
};

renderApp(App, document.getElementById('app'));

Inputs API

import { InputField, Textarea, Slider, ColorInput } from "mates-ui";
Prop Type Default Description
atom AtomType<string> Reactive atom binding. Two-way sync. Supersedes value.
attr AttrMap HTML attributes on the root element.
autocomplete string HTML autocomplete attribute value.
classes string Extra CSS class name(s) appended to the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
disabled boolean false Disables the field.
error boolean false Puts the field into an error state (red outline + icon).
errorText string Replaces helperText when error is true.
helperText string Hint text rendered below the field.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string Floating label text shown above the input.
leadingIcon string Material Symbols ligature name rendered on the left.
loading boolean false Shows a spinner in the trailing slot.
on OnEventMap DOM event handlers on the root element.
onChange (e: Event, value: string) => void Typed change shorthand.
onClick (e: MouseEvent) => void Shorthand for `on.click` (`on.click` wins when both are set).
onInput (e: Event, value: string) => void Typed input shorthand; debounce-aware when debounceMS is set.
placeholder string Placeholder text shown when the field is empty.
prefix string Non-editable text prepended inside the input.
size "sm" | "md" | "lg" "md" Controls field height and font size.
style StyleMap Root inline CSS (`style` prop; `style` wins if both set).
suffix string Non-editable text appended inside the input.
trailingIcon string Material Symbols ligature name rendered on the right.
type "text" | "email" | "password" | "search" | "number" | "tel" | "url" "text" HTML input type.
value string Controlled value of the input.
variant "outlined" | "filled" "outlined" Visual style of the field.

Textarea

Prop Type Default Description
atom AtomType<string> Reactive atom binding. Two-way sync. Supersedes value.
attr AttrMap HTML attributes on the root element.
classes string Extra CSS class name(s) appended to the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
disabled boolean false Disables the textarea.
error boolean false Error state.
errorText string Replaces helperText when error is true.
helperText string Hint text below the field.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string Floating label text.
maxLength number HTML maxlength attribute; also used by showCount.
on OnEventMap DOM event handlers on the root element.
onChange (e: Event, value: string) => void Typed change shorthand.
onClick (e: MouseEvent) => void Shorthand for `on.click` (`on.click` wins when both are set).
onInput (e: Event, value: string) => void Typed input handler; called with the current value.
rows number 3 Initial visible row count.
showCount boolean false Renders a character counter using maxLength.
style StyleMap Root inline CSS (`style` prop; `style` wins if both set).
value string Controlled value.
variant "outlined" | "filled" "outlined" Visual style.

Slider

Prop Type Default Description
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
disabled boolean false Disables the slider.
format (v: number) => string Formats the displayed value string.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string Accessible label rendered above the track.
labelSuffixErrors boolean false Appends validation errors to the label line.
max number 100 Maximum allowed value.
min number 0 Minimum allowed value.
on OnEventMap DOM event handlers on the root element.
showValue boolean false Renders the formatted value next to the label.
step number 1 Increment between legal values.
value* number Current slider value.

ColorInput

Prop Type Default Description
ariaLabel string aria-label when no visible label is provided.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
disabled boolean false Disables the colour picker.
error boolean false Error state styling.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string Visible label text.
on OnEventMap DOM event handlers on the root element.
size "sm" | "md" | "lg" "md" Controls swatch and field height.
value string Hex color string (#rrggbb). Always lowercase.

Caveats

  • Use atom binding (atom prop) for two-way reactivity — cleaner than the manual events.onInput pattern.
  • onInput fires after debounceMS delay when set; events.onInput.input fires synchronously on every keystroke.
  • ColorInput always returns a lowercase #rrggbb string; normalise with .toLowerCase() if sourcing values from other APIs.