Formatering
TextField kan formatere innholdet automatisk mens brukeren fyller ut — beløp med tusenskille, telefonnummer i grupper, kontonummer, fødselsnummer. Denne siden forklarer hvordan formatering virker, de to modusene, de tre måtene å definere en formatter på, og tilgjengelighetshensynene bak. For vanlig bruk og API, se TextField.
Vi formaterer, vi masker ikke
Uansett modus vises alt brukeren skriver. Formateringen avviser aldri et tastetrykk og skjuler aldri et tegn. Skriver brukeren flere tegn enn formatet har plass til — eller noe som ikke hører hjemme (en bokstav i et sifferfelt) — vises den ledende delen som passer formatert, og resten legges uformatert på til slutt, i samme rekkefølge. Et telefonfelt (000 00 000) viser 1234567890 som 123 45 67890, og 1234567a som 123 45 67a.
Feil fanges derfor av validering, ikke ved å droppe tegn. Den rå verdien bevarer alt brukeren skrev (minus separatorene formatteren setter inn), så en feilaktig bokstav overlever helt fram til valideringen din og kan gi en tydelig feilmelding.
Formateringen er kun presentasjon. onChange, ix-field.rawValue og form-innsending gir deg verdien uten separatorer ("12345678", ikke "123 45 678"). Du lagrer og validerer den rå verdien.
Et formatert felt kobles med {...register('felt')} akkurat som et uformatert (Mønster A) — se Form-validering. I formatter-modus videresender TextField et lite proxy-objekt til ref i stedet for den native <input> (siden ix-field eier den synlige DOM-verdien): proxyens value er alltid rå, og focus() delegerer til den synlige inputen. Det er en intern detalj — trenger du verdien selv, les den via onChange (event.target.value) eller ix-field.rawValue, ikke via ref.value direkte.
To moduser: blur (standard) og live (opt-in)
- Blur (standard): feltet viser formatert verdi når det ikke har fokus, og den rå (uformaterte) verdien når det får fokus, slik at brukeren kan redigere fritt uten at markøren hopper. Egne pattern-strenger og
{format,parse}-objekter er blur med mindre de opter inn på live. - Live (opt-in): separatorene bygger seg opp mens brukeren skriver, og markøren styres så den holder plassen. Fem av de innebygde variantene (
phone,amount,account,orgnr,ssn) formaterer live.dateer unntaket — den er skilletegn-bevisst og formaterer på blur (se tabellen under).
Modus bæres av et live-flagg på selve formatteren — det er ingen egen prop eller attributt på feltet.
Hvorfor er blur standard? Live reformatering for hvert tegn (klassisk «input-masking», slik Cleave.js og imask gjør) har kjente tilgjengelighetsulemper som blur unngår:
- Markøren kan hoppe. Redigerer du et tegn midt i strengen, sender mange løsninger markøren til slutten. Blur rører aldri verdien mens du skriver; Indeks sin live-modus styrer markøren eksplisitt for å holde plassen.
- Skjermlesere kan lese hele feltet på nytt hver gang en separator settes inn under skriving (WCAG 4.1.3 / 3.3.2).
- Transkribering blir vanskeligere. Skriver du av et nummer fra et kort eller dokument, kan løpende reformatering forstyrre sammenligningen med kilden (jf. GOV.UK sin frarådning mot å reformatere telefonnummer mens brukeren skriver).
Mange designsystemer (Aksel, Digdir, Adobe Spectrum, GOV.UK m.fl.) shipper derfor ingen live maske som standard. Indeks tilbyr live som et bevisst opt-in for de formatene der effekten er ønsket (særlig beløp), men beholder blur som trygg standard. Merk at Indeks sin live-modus formaterer — den masker ikke: den avviser aldri tastetrykk, i motsetning til klassisk maskering (som kan bryte WCAG 3.3.1 ved stille å nekte «ugyldige» tegn).
Overstyr modus per felt
Formatterens live-flagg bestemmer standardmodus, men du kan overstyre den på det enkelte feltet med formatLive (React) / data-format-live (HTML) — nyttig for å slå live av på en innebygd variant, eller på for en egen pattern uten å skrive en formatter.
Kode
<> <TextField label="Telefon (live av)" description="8 siffer, f.eks. 123 45 678" format="phone" formatLive={false} inputMode="numeric" defaultValue="12345678" /> <TextField label="Dato (live på)" description="dd.mm.åååå, f.eks. 24.12.2026" formatPattern="00.00.0000" formatLive inputMode="numeric" /> </>
Uten formatLive gjelder formatterens egen default (innebygde varianter er live, egne pattern/objekt er blur).
Tre måter å definere en formatter
En formatter er et par rene funksjoner: format(raw) lager visningsstrengen, parse(display) gjør den om til rå verdi igjen. parse er tapsfri — den fjerner kun separatorene format setter inn og beholder alt annet, så parse(format(raw)) gir tilbake raw for enhver verdi (også en med et feilaktig tegn). Et valgfritt live-flagg ({ format, parse, live: true }) slår på live-modus for formatteren.
Du kan angi formatteren på tre måter, i økende grad av fleksibilitet.
1. Innebygd variant (format="navn")
De vanligste behovene er innebygd. De fem fast-bredde variantene formaterer live; date er unntaket (skilletegn-bevisst, formaterer på blur):
| Variant | Formaterer | Modus | Eksempel |
|---|---|---|---|
phone | Norsk telefonnummer | live | 123 45 678 |
amount | Beløp med tusenskille | live | 1 234 567,89 |
account | Kontonummer | live | 1234 56 78903 |
orgnr | Organisasjonsnummer | live | 123 456 789 |
ssn | Fødselsnummer | live | 010190 12345 |
date | Dato — godtar 1.1.2026, nullutfyller på blur | blur | 24.12.2026 |
Kode
<TextField label="Telefonnummer" description="8 siffer, f.eks. 123 45 678" format="phone" type="tel" inputMode="numeric" autoComplete="tel-national" defaultValue="12345678" />
2. Pattern-streng (formatPattern="...")
For enkle mønstre uten kode. Hvert tegn i pattern-strengen er enten en plass brukeren fyller, eller en fast separator:
| Tegn | Betyr |
|---|---|
0 | ett siffer |
a | én bokstav (inkl. æ ø å) |
* | hvilket som helst tegn |
| alt annet | fast separator (settes inn automatisk) |
Kode
<TextField label="Kontonummer" description="11 siffer, f.eks. 1234 56 78903" formatPattern="0000 00 00000" inputMode="numeric" defaultValue="12345678903" />
formatPattern er ikke det native pattern-attributtetformatPattern styrer visning. Det native pattern-attributtet (en valideringsregex) sendes fortsatt uendret videre til <input>. De to er uavhengige.
3. Egen funksjon (format={{ format, parse }})
For logikk som ikke passer et pattern — locale-avhengige beløp, egne grupperinger, versaler:
Kode
<TextField label="Referanse" description="Store bokstaver, f.eks. AB-123" defaultValue="ab-123" format={{ format: (raw) => raw.toUpperCase(), parse: (display) => display.toLowerCase(), }} />
Egne, delbare varianter
Trenger teamet ditt en variant flere felter skal dele — som ikke er blant de innebygde — kan du registrere den én gang og bruke den overalt via format="<navn>". Da slipper du å sende inn funksjonen på hvert felt, og du er ikke avhengig av at designsystemet legger den til.
Registrering skjer på web component-laget (IxField), som React-komponenten bygger på:
import { IxField, createPatternFormatter } from '@sb1/indeks-web';
// Registrer én gang ved oppstart av appen (eksempel: KID-nummer):
IxField.registerFormatter('kid', createPatternFormatter('0000 0000 0000 000'));
<!-- Deretter, hvor som helst: -->
<ix-field>
<label>KID-nummer</label>
<span data-field="description">13 siffer, f.eks. 1234 5678 9012 3</span>
<div class="ix-text-field"><input inputmode="numeric" data-format="kid" /></div>
<span data-field="error"></span>
</ix-field>
createPatternFormatter og createAmountFormatter er eksportert fra @sb1/indeks-web for å bygge egne formattere, men du kan også sende inn et hvilket som helst { format, parse }-objekt.
Presedens
Setter du flere kilder samtidig, gjelder rekkefølgen: funksjon-property → data-format → data-format-pattern. Altså vinner en direkte satt formatter over et registrert navn, som igjen vinner over en pattern-streng.
Bruk uten React (HTML / web component)
Formateringen aktiveres av data-format på <input>, så den virker helt uten React:
<ix-field>
<label>Telefonnummer</label>
<span data-field="description">8 siffer, f.eks. 123 45 678</span>
<div class="ix-text-field">
<input type="tel" inputmode="numeric" data-format="phone" value="12345678" name="tlf" />
</div>
<span data-field="error"></span>
</ix-field>
Synlig input + skjult rå-mirror
Når en formatter er aktiv viser den synlige <input> den formaterte teksten, mens ix-field legger til en skjult <input type="hidden"> som bærer den rå verdien. Navnene byttes: den skjulte mirror-inputen overtar feltets name, og den synlige får ${name}_formatted.
Det gir to ting: <form>-innsending / FormData sender rå verdi under det opprinnelige navnet, og du kan hente den formaterte visningen med ${name}_formatted om du trenger den.
const field = document.querySelector('ix-field');
// Bekvemmelighets-getter på ix-field — alltid rå:
field.rawValue; // "12345678"
// Native form-innsending sender rå verdi under opprinnelig name:
const data = new FormData(document.querySelector('form'));
data.get('tlf'); // "12345678" (ikke "123 45 678")
// Den formaterte visningen ligger under ${name}_formatted:
data.get('tlf_formatted'); // "123 45 678"
input.value er ikke lenger garantert råDen synlige inputen viser formatert tekst (og rå ved fokus i blur-modus), så les rå verdi via field.rawValue eller den skjulte mirror-inputen — ikke synligInput.value.
Tilgjengelighet
- Bruk aldri
type="number"med formatering. Med separatorer i verdien returnerer nettleseren tom.value. Bruktype="text"ellertype="tel"medinputMode="numeric"/"decimal". - Kommuniser forventet format som tekst, i
descriptioneller label — ikke som placeholder-«understreker».description="11 siffer"er både synlig og lest opp av skjermleser. - Formatering er ikke validering. Feltet avviser aldri tastetrykk stille. Valider på blur eller innsending, og gi en tydelig feilmelding via
errorMessage. - Sett riktig
autocomplete(WCAG 1.3.5) — f.eks.autocomplete="tel-national"for telefonnummer.
Migrering fra Cleave.js og react-number-format
Bruker appen din cleave.js (utdatert) eller react-number-format for felt-formatering, kan disse erstattes:
| Tidligere | Nå |
|---|---|
Cleave { blocks, delimiter } for SSN/telefon | format="ssn" / format="phone" eller formatPattern |
Cleave { numeral: true, delimiter, numeralDecimalMark } | format="amount" |
react-number-format NumericFormat (tusenskille/desimaler) | format="amount" |
Egen format-funksjon | format={{ format, parse }} |
Indeks eksponerer alltid den rå verdien (i onChange, rawValue og form-innsending) — så nedstrøms kode som allerede lagrer råverdien (rawValue/floatValue) fungerer likt. De innebygde variantene formaterer live, slik at overgangen fra et Cleave-felt med tusenskille kjennes likt for brukeren.
Cleave/imask nekter typisk «ugyldige» tegn mens man skriver. Indeks gjør det motsatte: vi formaterer men masker ikke, så alt brukeren skriver vises, og feil fanges av validering. Sørg for at feltet har validering + errorMessage for verdier som må avvises.
Relatert
- TextField — bruk og fullt API
- ValidationMessage — vise valideringsfeil