# Find and confirm an address

Build address search using the selected address and geocoder capabilities.

Import: @slices.sh/geocoder/headless

Public parts: 50

## Installation

### 1. Install the service registry

services/index.ts, which `import "@/services"` loads. A provider setup writes the same file; skip this if your app has one.

```bash
npx shadcn@latest add "https://slices.sh/r/services.json"
```

### 2. Install the headless library

```bash
npm install @slices.sh/geocoder@^0.1.3
```

### 3. Install the service

Install the provider configuration via shadcn or the slices CLI. Skip this step if your app already has the service configured. Provider commands and provisioning: https://slices.sh/docs/services/geocoder.md

## Anatomy

### Composition

```text
Address.Root layout="card"
  Address.UndoScope
    Address.Label
    Address.Input
    Address.Clear
    Address.Popup
      Address.Suggestions
        Address.Recent
        Address.RecentItems
          Address.RecentAddress
        Address.RecentSeparator
        Address.Items
          Address.Suggestion
            Address.Match
    Address.Notes
      Address.Searching
      Address.Resolving
      Address.TooShort
      Address.NoMatches
      Address.ResultCount
      Address.Failed
        Address.Retry
    Address.Attribution
    Address.Layout mode="narrow"
      Address.UseMyLocation
    Address.LocationNote
    Address.EnterByHand
    Address.Entry
      Address.FieldOrder
        Address.FieldGroup field="city"
          Address.Field field="city"
        Address.FieldChoice field="country"
        Address.FieldSuggestions field="city"
      Address.NeedsNumber
      Address.PhoneRoot
        Address.Phone
      Address.Save
    Address.FilledMark
      Address.Undo
    Address.Chosen
    Address.Settled
      Address.Lines
        Address.Line
      Address.Change
    Address.MapRoot
      Address.Map
      Address.MapStatus
      Address.MapAttribution
    Address.Accuracy
    Address.Value name="shipping"
```

## Address suggestions

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { Address } from "@slices.sh/geocoder/headless"

export function Example() {
  return (
    <Address.Root country="FR">
      <Address.Label>Address</Address.Label>
      <Address.Input />
      <Address.Popup>
        <Address.Suggestions>
          <Address.Items>
            <Address.Suggestion>
              {({ primary, secondary }) => (
                <span>
                  {primary} · {secondary}
                </span>
              )}
            </Address.Suggestion>
          </Address.Items>
        </Address.Suggestions>
        <Address.Attribution />
      </Address.Popup>
      <Address.Value name="address" />
    </Address.Root>
  )
}

```

Selection, keyboard navigation and the structured value stay connected.

## Loading & empty states

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { Address } from "@slices.sh/geocoder/headless"

export function Example() {
  return (
    <Address.Root country="FR">
      <Address.Label>Address</Address.Label>
      <Address.Input />
      <Address.Searching>Looking up addresses…</Address.Searching>
      <Address.NoMatches>No matching addresses.</Address.NoMatches>
      <Address.Failed>Address search is unavailable.</Address.Failed>
    </Address.Root>
  )
}

```

Add these states beside the suggestions in the first example.

## A structured address

```tsx
"use client"

import "@/services"
import { Address } from "@slices.sh/geocoder/headless"

export function Example() {
  return (
    <Address.Root>
      <Address.EnterByHand>Enter address manually</Address.EnterByHand>
      <Address.Entry>
        <Address.FieldGroup field="city">
          <Address.Field field="city" aria-label="City" />
        </Address.FieldGroup>
        <Address.FieldGroup field="postcode">
          <Address.Field field="postcode" aria-label="Postal code" />
        </Address.FieldGroup>
        <Address.Save>Use this address</Address.Save>
      </Address.Entry>
      <Address.Chosen>
        <Address.Lines>
          <Address.Line />
        </Address.Lines>
        <Address.Change>Change address</Address.Change>
      </Address.Chosen>
    </Address.Root>
  )
}

```

The geocoder config supplies address structure and suggestions. Manual entry stays in the same address scope.

## Locate and preview

```tsx
"use client"

import "@/services"
import { Address } from "@slices.sh/geocoder/headless"

export function Example() {
  return (
    <Address.Root>
      <Address.Input aria-label="Address" />
      <Address.UseMyLocation>Use my location</Address.UseMyLocation>
      <Address.LocationNote />
      <Address.MapRoot>
        <Address.Map />
        <Address.MapStatus />
        <Address.MapAttribution />
      </Address.MapRoot>
    </Address.Root>
  )
}

```

Map preview also requires map-tiles and assets configurations. Install map-tiles.openfreemap and assets.cdn using their setup URLs. Location requires browser permission.

## Complete API reference

### Address.Root

scope

An address field.

The popup, its keyboard and its ARIA are Base UI's Autocomplete rather than this family's:
the hand-rolled version this replaces latched shut after a blur, an Escape or a selection
and never showed a suggestion again. Autocomplete and not Combobox because the value has
to be allowed to be anything — plenty of real addresses are in no database, which is this
family's whole argument — and `filter={null}` because the filtering is the provider's.

Base UI's own context holds the open state and the highlight. That is a primitive's
internal state rather than a slices concern, the same relationship `lib/part` already has
with `use-render`, and the hooks below still own one thing each.
→ docs/rules/one-hook-one-concern.md

Import: @slices.sh/geocoder/headless

```tsx
AddressRootProps

type AddressRootProps = RootProps & AddressFormOptions & {
  /**
   * Left out or `true`: required, and a missing geocoder throws. `"optional"`: renders its
   * children and the field is a manual address form: the street box is a plain input, the
   * search parts render nothing, and Entry, Field, Lines, Value and Save keep working.
   * `false`: off, which looks the same. `"reverse"`: requires a geocoder that can turn a
   * point into an address (otherwise UseMyLocation renders nothing).
   */
  enabled?: Enabled
  /** The geocoder config, `"default"` when left out. */
  config?: string
  /** Bias towards a country, as ISO 3166-1 alpha-2. */
  country?: string
  /** The language the names come back in. Absent asks with the browser's own. */
  language?: string
  /** Enter selects the first suggestion only when explicitly enabled. */
  enterPicksFirst?: boolean
  undoWindow?: number
  /** Bias towards a place. "Use my location" supplies one as soon as it is allowed. */
  near?: Point
  /** Only these kinds, where a form wants nothing coarser than a street. */
  kinds?: AddressKind[]
  recent?: RecentAddressSource
  recentStorageKey?: string
  initial?: Address
  /** The address as it stands, after every correction — not only when one is chosen. */
  onChange?(address: Address | null): void
}
```

Source: @slices.sh/geocoder/src/headless/address-root.tsx

### Address.UndoScope

content · Element: div

No document listener: another address or another form keeps its own undo history.

Import: @slices.sh/geocoder/headless

Address.UndoScope must be inside <Address.Root>.

```tsx
PartProps<"div">
```

Source: @slices.sh/geocoder/src/headless/undo-scope.tsx

### Address.Label

content · Element: label

The visible label for the box, or for one of the fields beside it.

A visible one, not an `aria-label`: a label somebody can click is a bigger target, which
is the whole point on a phone.

Import: @slices.sh/geocoder/headless

Address fields must be inside <Address.Root>.

Address.Label must be inside <Address.Root>.

```tsx
PartProps<"label", { optional: boolean; kind: string | null }> & { field?: AddressDetailFieldName }
```

Source: @slices.sh/geocoder/src/headless/label.tsx

### Address.Input

input · Element: input

The box itself, and the street line of the address.

`role`, `aria-expanded`, `aria-controls` and `aria-activedescendant` are the primitive's
now. What is set here is what the primitive has no opinion about: the autofill token, so
the browser's own one-tap address fill works and SC 1.3.5 is satisfied; the keyboard hints
a phone reads; and the link to the notes underneath, which have no other way to be heard.

Import: @slices.sh/geocoder/headless

Address.Input must be inside <Address.Root>.

```tsx
PartProps<"input">
```

Source: @slices.sh/geocoder/src/headless/address-input.tsx

### Address.Clear

action · Element: button

Empties the box.

The primitive's own Clear, which keeps focus in the field rather than dropping it to the
body, and hides itself while there is nothing to clear. Emptying the box is an ordinary
value change as far as this family is concerned, so there is nothing to do here but let
it through.

Import: @slices.sh/geocoder/headless

```tsx
PartProps<"button">
```

Source: @slices.sh/geocoder/src/headless/clear.tsx

### Address.Popup

content · Element: div

Where the suggestions appear.

A positioned layer rather than an absolutely-positioned block, which is what buys the
three things the hand-rolled list did not have: it flips above the box when there is no
room below, it publishes `--anchor-width` and `--available-height` for the skin to size
itself by, and it scrolls the highlighted option into view — browsers do not scroll to an
`aria-activedescendant`, so somebody has to.

Import: @slices.sh/geocoder/headless

Address.Popup must be inside <Address.Root>.

```tsx
PartProps<"div">
```

Source: @slices.sh/geocoder/src/headless/popup.tsx

### Address.Suggestions

content · Element: ul

The list.

Mounted for as long as the popup is, so `aria-controls` on the box names something that
exists whenever the box says it is expanded. The element itself comes from the caller —
a neutral layer renders no host element — and `role="listbox"` is the primitive's.

Import: @slices.sh/geocoder/headless

Address.Suggestions must be inside <Address.Root>.

```tsx
PartProps<"ul">
```

Source: @slices.sh/geocoder/src/headless/suggestions.tsx

### Address.Recent

content · Element: li

A non-selectable heading inside the autocomplete list, never above the field.

Import: @slices.sh/geocoder/headless

Address.Recent must be inside <Address.Root>.

```tsx
PartProps<"li">
```

Source: @slices.sh/geocoder/src/headless/recent.tsx

### Address.RecentItems

scope

Only while the street is empty and nothing is chosen. Recent is the non-selectable heading inside the list, RecentItems repeats its children per saved address, RecentAddress is a button that fills the whole address and returns focus to the street, and RecentSeparator divides them from live suggestions.

Import: @slices.sh/geocoder/headless

Address.RecentItems must be inside <Address.Root>.

```tsx
RootProps
```

Source: @slices.sh/geocoder/src/headless/recent-items.tsx

### Address.RecentAddress

action · Element: button

Only while the street is empty and nothing is chosen. Recent is the non-selectable heading inside the list, RecentItems repeats its children per saved address, RecentAddress is a button that fills the whole address and returns focus to the street, and RecentSeparator divides them from live suggestions.

Import: @slices.sh/geocoder/headless

Address.RecentAddress must be inside <Address.Root>.

Address.RecentAddress must be inside <Address.RecentItems>.

```tsx
PartProps<"button">
```

Source: @slices.sh/geocoder/src/headless/recent-address.tsx

### Address.RecentSeparator

content · Element: li

Only while the street is empty and nothing is chosen. Recent is the non-selectable heading inside the list, RecentItems repeats its children per saved address, RecentAddress is a button that fills the whole address and returns focus to the street, and RecentSeparator divides them from live suggestions.

Import: @slices.sh/geocoder/headless

Address.RecentSeparator must be inside <Address.Root>.

```tsx
PartProps<"li">
```

Source: @slices.sh/geocoder/src/headless/recent-separator.tsx

### Address.Items

scope

Each suggestion in its own scope.

Import: @slices.sh/geocoder/headless

Address.Items must be inside <Address.Root>.

```tsx
RootProps
```

Source: @slices.sh/geocoder/src/headless/suggestion-items.tsx

### Address.Suggestion

content · Element: li

One option.

The element comes from the caller, because a neutral layer renders no host element, and
`role="option"` is the primitive's. `aria-selected` is not: the primitive leaves it off
because its list is not a selection. That attribute is load-bearing: Chrome plus
VoiceOver announces a moving `aria-activedescendant` only when the option it names
carries `aria-selected="true"`, so without it the box is silent on the most common
pairing there is.

Import: @slices.sh/geocoder/headless

Address.Suggestion must be inside <Address.Items>.

Address.Suggestion must be inside <Address.Root>.

```tsx
PartProps<"li", SuggestionState>

type SuggestionState = {
  active: boolean
  /** The street line, which is what tells two Rue de Rivolis apart from each other. */
  primary: string
  /** Everything after it, which is what tells them apart from each other's towns. */
  secondary: string
}
```

Source: @slices.sh/geocoder/src/headless/suggestion.tsx

### Address.Match

content · Element: span

The matched text is styled by a role; neither query nor provider text becomes HTML.

Import: @slices.sh/geocoder/headless

Address.Match must be inside <Address.Items>.

Address.Match must be inside <Address.Root>.

```tsx
PartProps<"span"> & { field: "primary" | "secondary" }
```

Source: @slices.sh/geocoder/src/headless/match.tsx

### Address.Notes

content · Element: div

Everything the box has to say, in one place that is always there.

One region, because the box's `aria-describedby` has to name an element that exists even
when there is nothing to say — and because two live regions announcing at once is how a
screen reader ends up reading the result count over the error. WCAG 2.2 SC 4.1.3.

Whatever a skin does to it, it must never be `display: none`, `hidden` or `aria-hidden`,
not even while it is empty: a region that appears in the same frame as its first words is
a region several screen readers say nothing about. Mounted, rendered, and empty is the
only shape that announces reliably — which is why everything the field has to say goes
in here rather than beside the control it is about.

Import: @slices.sh/geocoder/headless

Address.Notes must be inside <Address.Root>.

```tsx
PartProps<"div">
```

Source: @slices.sh/geocoder/src/headless/notes.tsx

### Address.Searching

gate

A question has been asked and not yet answered.

Import: @slices.sh/geocoder/headless

Address.Searching must be inside <Address.Root>.

```tsx
GateProps
```

Source: @slices.sh/geocoder/src/headless/searching.tsx

### Address.Resolving

gate

Gates and triggers for each answer. Searching: a question asked and not answered. Resolving: the optional detail lookup after a choice. TooShort: under the three-character floor, children get { least }. NoMatches: the provider answered with nothing. Failed gets { error }, the GeocoderErrorSlug, so a throttle and an outage are not one sentence. Retry appears only after a refusal and asks again.

Import: @slices.sh/geocoder/headless

Address.Resolving must be inside <Address.Root>.

```tsx
GateProps
```

Source: @slices.sh/geocoder/src/headless/resolving.tsx

### Address.TooShort

gate

Typed, but not enough of it to ask yet.

The one state with nothing on screen and a reason for it, which is exactly the message
no markup can carry and a screen reader therefore never hears unless somebody says it.

Import: @slices.sh/geocoder/headless

Address options must be inside <Address.Root>.

Address.TooShort must be inside <Address.Root>.

```tsx
GateProps<{ least: number }>
```

Source: @slices.sh/geocoder/src/headless/too-short.tsx

### Address.NoMatches

gate

The geocoder found nothing.

Which is not the end of the road: the fields are there to be typed into, because a
geocoder that has never heard of somebody's address must not be the reason they cannot
check out.

Import: @slices.sh/geocoder/headless

Address options must be inside <Address.Root>.

Address.NoMatches must be inside <Address.Root>.

```tsx
GateProps
```

Source: @slices.sh/geocoder/src/headless/no-matches.tsx

### Address.ResultCount

content · Element: span

How many suggestions there are, said out loud.

A screen reader hears the highlighted option read out and is told nothing about how many
others there are, or that a query is still too short to have asked. The markup does not
carry either, which is why this part exists and why `Address.TooShort` has words of its
own. WCAG 2.2 SC 4.1.3.

Import: @slices.sh/geocoder/headless

Address.ResultCount must be inside <Address.Root>.

```tsx
PartProps<"span", CountState>

type CountState = {
  count: number
  /** Which option the keyboard is on, from 1, or 0 for none. */
  position: number
  /** What that option says, because "3 of 6" on its own names nothing. */
  label: string
}
```

Source: @slices.sh/geocoder/src/headless/result-count.tsx

### Address.Failed

content · Element: p

The geocoder refused, with the reason.

The reason is handed over rather than flattened into one sentence: telling somebody the
service is down when they have been throttled, and there is a Retry beside it, is a small
lie. → docs/rules/capability-honesty.md

Import: @slices.sh/geocoder/headless

Address.Failed must be inside <Address.Root>.

```tsx
PartProps<"p", { error: GeocoderErrorSlug }>
```

Source: @slices.sh/geocoder/src/headless/search-failed.tsx

### Address.Retry

action · Element: button

Asks the same question again, which is the only useful answer to being throttled.

Import: @slices.sh/geocoder/headless

Address.Retry must be inside <Address.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/geocoder/src/headless/retry.tsx

### Address.Attribution

content · Element: a

Always show the active provider's credit, including after its popup closes.

It opens in a new tab: following it in this one would leave the form somebody is halfway
through.

Import: @slices.sh/geocoder/headless

```tsx
PartProps<"a">
```

Source: @slices.sh/geocoder/src/headless/attribution.tsx

### Address.Layout

content

Which of the two shapes this screen is, for the part of a layout CSS cannot reach.

Width is a stylesheet's job — every size, column count and hit area here is a media
query. Words are not: a button that is the width of a phone says "Use my location" and
the same button beside a desktop field says "Use my current location", and no breakpoint
can rewrite a sentence. Both are written out, and exactly one of them is in the DOM.

Import: @slices.sh/geocoder/headless

Address.Layout must be inside <Address.Root>.

```tsx
PartProps<null> & { mode: "wide" | "narrow" }
```

Source: @slices.sh/geocoder/src/headless/layout.tsx

### Address.UseMyLocation

action · Element: button

One tap instead of an address.

`locating` is handed over because a position takes as long as it takes — ten seconds on a
phone that has to wake its radio — and a dimmed button is not an answer to "is it
working?". The words change instead.

Renders nothing at all where the browser has no geolocation, or the provider cannot
reverse a point. A refusal keeps the focused control mounted and disabled, with the
explanation in LocationNote. → docs/rules/capability-honesty.md

Import: @slices.sh/geocoder/headless

Capability: reverse

Address.UseMyLocation must be inside <Address.Root>.

```tsx
PartProps<"button", { locating: boolean }>
```

Source: @slices.sh/geocoder/src/headless/use-my-location.tsx

### Address.LocationNote

content · Element: p

What happened when the browser was asked where it is.

Three answers worth different words: a permission denied, which nothing here can undo; a
point with nothing mapped at it, which for most of the ocean is the truth; and a lookup
that simply failed. The state is handed over rather than flattened.

Import: @slices.sh/geocoder/headless

Capability: reverse

Address.LocationNote must be inside <Address.Root>.

```tsx
PartProps<"p", { state: Told }>

type Told = "refused" | "nowhere" | "failed"
```

Source: @slices.sh/geocoder/src/headless/location-note.tsx

### Address.EnterByHand

action · Element: button

Opens the fields without choosing anything, for an address no geocoder has.

Import: @slices.sh/geocoder/headless

Address.EnterByHand must be inside <Address.Root>.

```tsx
PartProps<"button"> & { inPopup?: boolean }
```

Source: @slices.sh/geocoder/src/headless/enter-by-hand.tsx

### Address.Entry

content · Element: div

The address is being filled in, field by field.

Open as soon as anything has been chosen, because a lookup nobody can see the rest of is
a lookup nobody can correct — and 9% of hand-typed addresses have a typo in them.

A group rather than a gate around one, because five boxes in a border are five unrelated
boxes to somebody hearing them one at a time. Give it a name and a screen reader says
what they belong to on the way in.

Import: @slices.sh/geocoder/headless

Address.Entry must be inside <Address.Root>.

```tsx
PartProps<"div">
```

Source: @slices.sh/geocoder/src/headless/entry.tsx

### Address.FieldOrder

content · Element: div

Reorder keyed field groups in the DOM, so reading and tab order agree with the layout.

Import: @slices.sh/geocoder/headless

Address.FieldOrder must be inside <Address.Root>.

```tsx
PartProps<"div"> & { children: ReactNode }
```

Source: @slices.sh/geocoder/src/headless/field-order.tsx

### Address.FieldGroup

content · Element: div

The rest of the address. Entry is the group of fields; give it an aria-label. EnterByHand opens it with nothing chosen. Field is one text input with its own autofill token and requiredness from the country's metadata; FieldChoice renders a native select where the metadata lists regions, and FieldSuggestions offers countries without preventing others. FieldGroup holds one field and its label, with details to keep it closed until manual entry; FieldOrder sorts its FieldGroup children into the country's order in the DOM. Save, in the card layout, checks the fields in reading order and folds them into the card.

Import: @slices.sh/geocoder/headless

Address.FieldGroup must be inside <Address.Root>.

```tsx
PartProps<"div"> & { field: AddressFieldName; details?: boolean }
```

Source: @slices.sh/geocoder/src/headless/field-group.tsx

### Address.Field

input · Element: input

One field of the address, other than the street line the box itself holds.

Each carries its own autofill token, which is what a browser and a password manager fill
on, and a postcode carries a number pad only where postcodes in that country are digits.

Import: @slices.sh/geocoder/headless

Address fields must be inside <Address.Root>.

Address.Field must be inside <Address.Root>.

```tsx
PartProps<"input"> & { field: AddressDetailFieldName; fallback?: boolean; invalidMessage?: string }
```

Source: @slices.sh/geocoder/src/headless/field.tsx

### Address.FieldChoice

input · Element: select

Native keyboard and mobile pickers; unrecognised provider values remain editable choices.

Import: @slices.sh/geocoder/headless

Address.FieldChoice must be inside <Address.Root>.

Address fields must be inside <Address.Root>.

```tsx
PartProps<"select"> & { field: AddressDetailFieldName; invalidMessage?: string }
```

Source: @slices.sh/geocoder/src/headless/field-choice.tsx

### Address.FieldSuggestions

content · Element: datalist

Suggestions keep manual entry possible outside the pinned country subset.

Import: @slices.sh/geocoder/headless

Address.FieldSuggestions must be inside <Address.Root>.

```tsx
PartProps<"datalist"> & { field: AddressDetailFieldName }
```

Source: @slices.sh/geocoder/src/headless/field-suggestions.tsx

### Address.NeedsNumber

content · Element: p

The address is not precise enough to deliver to.

"Rue de Rivoli" is a real answer to a real query and a parcel cannot be sent to it — the
five Rivolis Photon returns have no house number between them, because the street is what
was mapped. Saying so beats letting somebody check out and find out later.

Import: @slices.sh/geocoder/headless

Address.NeedsNumber must be inside <Address.Root>.

```tsx
PartProps<"p", { kind: AddressKind }>
```

Source: @slices.sh/geocoder/src/headless/needs-number.tsx

### Address.PhoneRoot

scope

The address's phone number, with its own country list.

Its default country is the address's, so a national number is read the way the address
writes one, and a new address country moves the default with it. Once somebody picks a
country, or types a `+`, the phone keeps that one: a French number goes with a United
States address. No phone value goes to the geocoder.

Import: @slices.sh/geocoder/headless

Capability: phone (service, optional)

Address.PhoneRoot must be inside <Address.Root>.

```tsx
PhoneRootProps

type PhoneRootProps = RootProps & {
  /** `false` turns the phone field off. Phone has no service to be missing, so nothing else changes it. */
  enabled?: Enabled
  /** Where a national number is read when the address has no country yet. */
  defaultCountry?: string
  /** The number the field starts with, as E.164. */
  initial?: string
  /** The countries a number may be in, which are also the ones the list offers. Without it, every country. */
  allowedCountries?: readonly string[]
  locale?: string
  /** E.164 once the number is valid and its country allowed, `null` otherwise. */
  onChange?(number: string | null): void
}
```

Source: @slices.sh/geocoder/src/headless/phone-root.tsx

### Address.Phone

content

The number, typed in its country's own format. Typing it is typing in the form, so it ends an undo's offer.

Import: @slices.sh/geocoder/headless

Capability: phone (service, optional)

Address.Phone must be inside <Address.Root>.

```tsx
PhoneProps

type PhoneProps = PartProps<"input", { invalid: boolean; status: PhoneStatus }> & { invalidMessage?: string }
```

Source: @slices.sh/geocoder/src/headless/address-phone.tsx

### Address.Save

action · Element: button

Collapses the fields to the card, once what is in them would be accepted.

Checked here because this is a button and not the form's submit: nothing else would say
that a postcode is wrong until somebody checked out. The first field that is empty or
invalid takes focus and says why in the browser's own words.
→ docs/rules/one-submit-per-form.md

Import: @slices.sh/geocoder/headless

Address.Save must be inside <Address.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/geocoder/src/headless/save.tsx

### Address.FilledMark

content · Element: div

After a choice or a recent address fills the fields. UndoScope confines Cmd/Ctrl+Z to this address, with no document listener. Undo restores the previous values and focus while its window lasts. FilledMark shows that fields were just filled, as data-state="filled" on their groups.

Import: @slices.sh/geocoder/headless

Address.FilledMark must be inside <Address.Root>.

```tsx
PartProps<"div">
```

Source: @slices.sh/geocoder/src/headless/filled-mark.tsx

### Address.Undo

action · Element: button

After a choice or a recent address fills the fields. UndoScope confines Cmd/Ctrl+Z to this address, with no document listener. Undo restores the previous values and focus while its window lasts. FilledMark shows that fields were just filled, as data-state="filled" on their groups.

Import: @slices.sh/geocoder/headless

Address.Undo must be inside <Address.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/geocoder/src/headless/undo.tsx

### Address.Chosen

gate

An address has been chosen from the suggestions.

In the card layout it waits for the card: while the box and its fields are open the address
is being corrected there, and a card beside them would say it twice. For the card of an
address nobody chose, typed and saved by hand, see `Address.Settled`.

Import: @slices.sh/geocoder/headless

Address.Chosen must be inside <Address.Root>.

```tsx
GateProps<{ address: Address }>
```

Source: @slices.sh/geocoder/src/headless/chosen.tsx

### Address.Settled

gate

The card layout has an address and is showing it as a card, in place of the box and the
fields.

Settled by choosing a suggestion or by saving what was typed, so a hand-typed address that
no geocoder has gets the same card as a chosen one.

Import: @slices.sh/geocoder/headless

Address.Settled must be inside <Address.Root>.

```tsx
GateProps<{ address: Address }>
```

Source: @slices.sh/geocoder/src/headless/settled.tsx

### Address.Lines

scope

The chosen address, one line per line an envelope would have.

Built from whichever fields the provider filled, so an address with no street number has
one fewer line rather than a blank one — most of the world has no street number.
→ docs/rules/capability-honesty.md

Import: @slices.sh/geocoder/headless

Address.Lines must be inside <Address.Root>.

```tsx
RootProps & { omitStreet?: boolean }
```

Source: @slices.sh/geocoder/src/headless/address-lines.tsx

### Address.Line

content · Element: span

Chosen opens once an address is chosen from the list, Settled once the card layout shows its card; both give { address }. Lines repeats once per envelope line and Line renders it; omitStreet leaves out the line the box already shows. Change is the pencil: it forgets the choice and focuses the box, or with edit opens the fields on the address as it stands.

Import: @slices.sh/geocoder/headless

Address.Line must be inside <Address.Lines>.

```tsx
PartProps<"span">
```

Source: @slices.sh/geocoder/src/headless/line.tsx

### Address.Change

action · Element: button

Puts the box back to searching, keeping what was typed. With `edit`, opens the fields on
the address as it stands instead, which is what a pencil on a card is for when there are
fields to correct it in.

Over the primitive's own trigger, which moves focus into the box. This button unmounts as
it is pressed — the chosen address is what it exists for — and focus would otherwise fall
to the body, leaving somebody who pressed "change" with nowhere to type. A card has
unmounted the box as well, so the trigger has nothing to focus there: the box comes back
with this press, and takes focus once it has.

Import: @slices.sh/geocoder/headless

Address.Change must be inside <Address.Root>.

```tsx
PartProps<"button"> & { edit?: boolean }
```

Source: @slices.sh/geocoder/src/headless/change.tsx

### Address.MapRoot

scope

A small map of the chosen point. MapRoot renders nothing until there is a point and map-tiles is installed. Map is the surface, role="img" with aria-busy while tiles load. MapStatus gets { status }. MapAttribution renders the map's credits that Attribution does not already show, and nothing when there are none.

Import: @slices.sh/geocoder/headless

Capability: map-tiles (service, optional)

Address.MapRoot must be inside <Address.Root>.

```tsx
MapRootProps

type MapRootProps = RootProps & {
  /** The map-tiles config, `"default"` when left out. */
  config?: string
  /**
   * Left out or `"optional"`: the map shows when there is a point, and says it is unavailable
   * when there are no tiles. `true`: requires map-tiles and throws without it. `false`: off.
   */
  enabled?: Enabled
}
```

Source: @slices.sh/geocoder/src/headless/map-root.tsx

### Address.Map

content · Element: div

A small map of the chosen point. MapRoot renders nothing until there is a point and map-tiles is installed. Map is the surface, role="img" with aria-busy while tiles load. MapStatus gets { status }. MapAttribution renders the map's credits that Attribution does not already show, and nothing when there are none.

Import: @slices.sh/geocoder/headless

Capability: map-tiles (service, optional)

Address.Map must be inside <Address.MapRoot>.

```tsx
PartProps<"div">
```

Source: @slices.sh/geocoder/src/headless/map.tsx

### Address.MapStatus

content · Element: p

A small map of the chosen point. MapRoot renders nothing until there is a point and map-tiles is installed. Map is the surface, role="img" with aria-busy while tiles load. MapStatus gets { status }. MapAttribution renders the map's credits that Attribution does not already show, and nothing when there are none.

Import: @slices.sh/geocoder/headless

Capability: map-tiles (service, optional)

Address.MapStatus must be inside <Address.MapRoot>.

```tsx
PartProps<"p", { status: MapPreviewStatus }>
```

Source: @slices.sh/geocoder/src/headless/map-status.tsx

### Address.MapAttribution

content · Element: div

The map's credits that `Address.Attribution` does not already show, each once. Nothing renders
when there are none, so a pattern can put its "Map credits" popover inside without ever
showing an empty one.

Import: @slices.sh/geocoder/headless

Capability: map-tiles (service, optional)

Address.MapAttribution must be inside <Address.MapRoot>.

```tsx
PartProps<"div", { sources: readonly Source[] }>
```

Source: @slices.sh/geocoder/src/headless/map-attribution.tsx

### Address.Accuracy

content · Element: span

How near the browser thinks it got, in metres.

The browser's own number, at 95% confidence, and shown only when it reported one. A
browser that says nothing gets no line rather than a plausible-looking estimate, because
"accurate to ~40 m" is the difference between the right building and the next one.

And only while the address on screen is still the one the browser found: a metre count
left over from a position, sitting under an address somebody has since searched for and
chosen, is a number about somewhere else. → docs/rules/capability-honesty.md

Import: @slices.sh/geocoder/headless

Capability: reverse

Address.Accuracy must be inside <Address.Root>.

```tsx
PartProps<"span", { metres: number }>
```

Source: @slices.sh/geocoder/src/headless/accuracy.tsx

### Address.Value

input · Element: input

What the form sends.

The whole address as JSON, because the parts of it that matter most have no field of their
own: the coordinate, the ISO country code, and which half of the street line was the house
number. Give it a `name` and a `<form>` posts it; read it with `JSON.parse` and it is an
`Address`. Empty while every field is, which is how a server tells "nothing entered" from
"entered nothing useful".

Import: @slices.sh/geocoder/headless

Address.Value must be inside <Address.Root>.

```tsx
PartProps<"input">
```

Source: @slices.sh/geocoder/src/headless/value.tsx
