@arkyn/templates doesn't export any components, it's the data layer meant to be plugged into Select, PhoneInput, and CurrencyInput from @arkyn/components. This guide shows how the pieces are meant to fit together, and clears up a couple of naming details that are easy to trip over.brazilianStates already matches Select's options shape ({ label, value }) exactly, so no mapping step is needed:tsx
import { Select } from "@arkyn/components/select";import { brazilianStates } from "@arkyn/templates";<Select name="state" label="State" options={brazilianStates} />
countries needs a small mapping step, since its objects carry more fields than Select expects:tsx
import { Select } from "@arkyn/components/select";import { countries } from "@arkyn/templates";const countryOptions = countries.map((country) => ({label: `${country.name} (${country.code})`,value: country.iso,}));<Select name="country" label="Country" options={countryOptions} isSearchable />
PhoneInput consumes @arkyn/templates' countries internally for its
built-in country selector (defaultCountryIso), you don't need to wire
that yourself. Reuse the same countries list for any other field on
the same form (like a "shipping country" Select) instead of hand-rolling
a second list, so both fields stay in sync if Arkyn ever updates its data.CurrencyInput's prop is called locale, but it actually expects a currency code ("USD", "BRL", "EUR", …), the same strings used as keys in countryCurrencies, not a language-region locale string like "pt-BR". countryCurrencies' own countryLanguage field is the actual BCP-47 locale, meant for Intl.NumberFormat, and is a different value entirely.tsx
import { CurrencyInput } from "@arkyn/components/currencyInput";import { countryCurrencies, maximumFractionDigits } from "@arkyn/templates";// countryCurrencies.BRL = { countryLanguage: "pt-BR", countryCurrency: "BRL" }function PriceField({ currencyCode }: { currencyCode: keyof typeof countryCurrencies }) {const { countryLanguage, countryCurrency } = countryCurrencies[currencyCode];return (<>{/* CurrencyInput's "locale" prop takes the currency code, not countryLanguage */}<CurrencyInput name="price" locale={countryCurrency as any} label="Price" />{/* Use countryLanguage for anything formatted with Intl.NumberFormat elsewhere on the page */}<span>{new Intl.NumberFormat(countryLanguage, {style: "currency",currency: countryCurrency,maximumFractionDigits,}).format(1234.5)}</span></>);}
CurrencyInput's locale dynamically from a country/currency picker, derive it from Object.keys(countryCurrencies) (or from the selected country's currency) rather than hardcoding a separate list of supported currencies, that way both stay backed by the same source of truth.PhoneInput's hidden submitted value is the selected country's dialing code concatenated directly with the entered digits, e.g. +5511999999999, which is the same E.164-style shape that @arkyn/shared's findCountryMask/formatToPhone and @arkyn/server's validatePhone expect as input. This isn't a coincidence: all three read from the same @arkyn/templates countries list.tsx
// Client: PhoneInput submits "+5511999999999" as-is<PhoneInput name="phone" label="Phone" defaultCountryIso="BR" />
typescript
// Server: validate and format the exact same value, no reformatting neededimport { validatePhone } from "@arkyn/server/validatePhone";import { formatToPhone } from "@arkyn/shared/formatToPhone";const phone = formData.get("phone") as string; // "+5511999999999"if (!validatePhone(phone)) {throw new BadRequest("Invalid phone number");}const displayPhone = formatToPhone(phone); // "(11) 99999-9999"
PhoneInput), use
findCountryMask
instead of writing your own country/mask matching, it already resolves
the correct entry from the same countries template.countries, brazilianStates, and countryCurrencies are plain data, there's no fetch, no async loading, and no peer dependency required to use them (unlike findCountryMask/formatToPhone, which need libphonenumber-js).mask field in countries is an array (currently only Brazil, for the with/without-ninth-digit variants) rather than a single string, code that reads country.mask directly should handle both shapes if it bypasses PhoneInput/findCountryMask.validatePhone check turns into a fieldErrors entry the client can display.SearchPlaces' returned stateShortName lines up with brazilianStates' value field, useful if you need to reconcile a free-text address search with a fixed state Select on the same form.