Skip to main content

Countries & Phone

CountriesDropdown — a Select-style picker with emoji flags and region sub-labels. PhoneInput — a phone field with an integrated dial-code country selector.

Quick start
import { CountriesDropdown } from "mates-ui";
import { PhoneInput }         from "mates-ui";





const country   = atom("us");
const phone     = atom("");
const countryId = atom("us");

${CountriesDropdown({
  label:    "Country",
  value:    country(),
  onChange: (id) => country.set(id),
})}

${PhoneInput({
  label:           "Phone number",
  value:           phone(),
  countryId:       countryId(),
  onInput:         (v) => phone.set(v),
  onCountryChange: (id) => countryId.set(id),
})}

Countries Dropdown — basic

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

const App = () => {
const sm = atom(""); const md = atom(""); const lg = atom("");
return () => html`
<x-row gap="var(--md-space-5)" wrap align="flex-start">
<x-col gap="var(--md-space-2)" style="min-width:11rem;">
${CountriesDropdown({ size: "sm", label: "Country (sm)", placeholder: "Pick…", value: sm(), onChange: (id) => sm.set(id) })}
${Text("Selected: " + (sm() || "—"), { type: "label-sm", color: "muted" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:13rem;">
${CountriesDropdown({ size: "md", label: "Country (md)", placeholder: "Pick…", value: md(), onChange: (id) => md.set(id) })}
${Text("Selected: " + (md() || "—"), { type: "label-sm", color: "muted" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:15rem;">
${CountriesDropdown({ size: "lg", label: "Country (lg)", placeholder: "Pick…", value: lg(), onChange: (id) => lg.set(id) })}
${Text("Selected: " + (lg() || "—"), { type: "label-sm", color: "muted" })}
</x-col>
</x-row>
`;
};

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

Countries Dropdown — variants

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

const App = () => {
const a = atom(""); const b = atom("");
return () => html`
<x-row gap="var(--md-space-5)" wrap>
<div style="min-width:13rem;">
${CountriesDropdown({ label: "Default", placeholder: "Select a country…", value: a(), onChange: (id) => a.set(id) })}
</div>
<div style="min-width:13rem;">
${CountriesDropdown({ label: "Another", placeholder: "Select a country…", value: b(), onChange: (id) => b.set(id) })}
</div>
</x-row>
`;
};

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

Countries Dropdown — disabled & error

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

const App = () => {
const err = atom("");
return () => html`
<x-row gap="var(--md-space-5)" wrap>
<div style="min-width:13rem;">
${CountriesDropdown({ label: "Disabled", placeholder: "Select…", disabled: true, value: "us", onChange: () => {} })}
</div>
<div style="min-width:13rem;">
${CountriesDropdown({ label: "Normal", placeholder: "Select…", value: err(), onChange: (id) => err.set(id) })}
</div>
</x-row>
`;
};

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

Phone Number Input — basic

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

const App = () => {
const sm = atom(""); const smC = atom("us");
const md = atom(""); const mdC = atom("gb");
const lg = atom(""); const lgC = atom("jp");
return () => html`
<x-row gap="var(--md-space-5)" wrap align="flex-start">
<x-col gap="var(--md-space-2)" style="min-width:13rem;">
${PhoneInput({ size: "sm", label: "Phone (sm)", value: sm(), countryId: smC(), onInput: (v) => sm.set(v), onCountryChange: (id) => smC.set(id) })}
${Text(smC() + " · " + (sm() || "—"), { type: "label-sm", color: "muted" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:15rem;">
${PhoneInput({ size: "md", label: "Phone (md)", value: md(), countryId: mdC(), onInput: (v) => md.set(v), onCountryChange: (id) => mdC.set(id) })}
${Text(mdC() + " · " + (md() || "—"), { type: "label-sm", color: "muted" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:17rem;">
${PhoneInput({ size: "lg", label: "Phone (lg)", value: lg(), countryId: lgC(), onInput: (v) => lg.set(v), onCountryChange: (id) => lgC.set(id) })}
${Text(lgC() + " · " + (lg() || "—"), { type: "label-sm", color: "muted" })}
</x-col>
</x-row>
`;
};

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

Phone Number Input — states

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

const App = () => {
const def = atom(""); const defC = atom("us");
const hlp = atom(""); const hlpC = atom("de");
const err = atom(""); const errC = atom("fr");
return () => html`
<x-row gap="var(--md-space-5)" wrap align="flex-start">
<div style="min-width:15rem;">
${PhoneInput({ label: "Default", value: def(), countryId: defC(), onInput: (v) => def.set(v), onCountryChange: (id) => defC.set(id) })}
</div>
<div style="min-width:15rem;">
${PhoneInput({ label: "With helper", helperText: "Include area code.", value: hlp(), countryId: hlpC(), onInput: (v) => hlp.set(v), onCountryChange: (id) => hlpC.set(id) })}
</div>
<div style="min-width:15rem;">
${PhoneInput({ label: "With error", error: true, errorText: "Please enter a valid phone number.", value: err(), countryId: errC(), onInput: (v) => err.set(v), onCountryChange: (id) => errC.set(id) })}
</div>
<div style="min-width:15rem;">
${PhoneInput({ label: "Disabled", disabled: true, value: "555-0100", countryId: "us", onInput: () => {}, onCountryChange: () => {} })}
</div>
</x-row>
`;
};

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

PhoneInput

import { PhoneInput } from "mates-ui";
Prop Type Default Description
attr AttrMap HTML attributes on the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
countryId string "us" Selected country id for the dial-code dropdown.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
disabled boolean false Disables the control.
error boolean false Error styling without a message.
errorText string Error message (replaces helper when set).
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.
on OnEventMap DOM event handlers on the root element.
onCountryChange (countryId: string, dialCode: string) => void Fired when the dial-code country changes.
onInput (value: string, e: Event) => void Fired on input — value first, then the event.
pattern string Mask pattern; `0` / `d` are digit slots.
placeholder string Input placeholder.
size "sm" | "md" | "lg" "md" Field size.
style StyleMap Inline CSS on the root element.
value string Controlled phone digits / formatted value.

Caveats

  • PhoneInput has no control-level `id` prop — use `id` / `data` / `attr` on the root, or pass through to the inner input via future first-class props.
  • Slots: root | label | row | dropdown | flag | code | input | supporting.