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.