> ## Oymyakon DS index
> Fetch the full index at: https://super-dollop-pzmo65r.pages.github.io/llms.txt
> HTML preview: https://super-dollop-pzmo65r.pages.github.io/dragging-map-pin.html
> Source: specs/components/dragging-map-pin/dragging-map-pin.md, specs/components/dragging-map-pin/dragging-map-pin-a11y.md, specs/components/dragging-map-pin/capabilities.json

---

# DraggingMapPin · Oymyakon DS 3

> The pin a person sets an address with by dragging the map under it — the pickup point (Point A) or the
> destination (Point B) — with an address tooltip above it.

**Version:** 3.0.1 · **Status:** Ready · **Figma:** `[MapPin] 3.0` (main components file `7vdl5YkZFDWvh9QvSmydsH`, page `14794:3465`; the set `DraggingMapPin` `15453:8077` (key `c874af92…`) — Type × State + the boolean `Address Tooltip`; its tooltip `MapPinTooltip` `15459:8603` (State × RTL) in "Building blocks" `15526:22350`; the loader `[Loader] S (20) / Style=Primary` `2046:8028`; the Specification frame `15619:16046`)

---

## 1. Description

DraggingMapPin marks the address a person is choosing on a map. The map moves under it; the pin stays at
the centre, lifts while the map is dragged and sets down when it stops. Above it, the Address Tooltip
names the place — "Pickup point · Address" — or shows that the address is loading or was not found
(Figma: "The purpose of a map pin is to highlight important places or addresses within a map-based
interface and make them easily identifiable to users").

Three types (the Figma `Type`):

- **Point A (RH)** — the pickup point for a ride; the passenger glyph. The tooltip's default Subtitle is
  "Pickup point".
- **Point A (Courier)** — the pickup point for a delivery. In Figma `[MapPin] 3.0` it is **the same pin
  as Point A (RH)** in every state — the same `onmap-a-pin-color` glyph; the web builds it as Figma has
  it (owner, 2026-11-03). Flagged: the courier glyph is the owner's to set in Figma.
- **Point B** — the destination; the flag glyph. The default Subtitle is "Destination".

The pin does not move on its own: the host moves the map and sets the pin's state — Drag while the map
moves, Address Loading while it asks for the address, then Standard with the address, or Error.

The other map elements on the `[MapPin] 3.0` page — the on-route pins (`OnRouteMapPointA`,
`OnRouteMapPointACourier`, `OnRouteMapPointB`, `OnRouteMapStopover`), the suggested pickup points
(`MapPinSuggestedPickup`) and the route — are not part of this spec; they stay Figma-only.

### Platform implementations

| Platform | Entry point | Version | Notes |
|---|---|---|---|
| iOS | — | — | Not in the iOS Design Context Bridge |
| Android | — | — | Not in the Android Design Context Bridge |
| Flutter | — | — | Not shipped |
| Web | `src/shared/shared.css` (`.dragging-map-pin`) | 3.0.0 | Reference implementation, this repo; preview page `src/dragging-map-pin.njk` |

---

## 2. Anatomy

```
DraggingMapPin (.dragging-map-pin)            s40 wide · its bottom centre is the location
├── Address Tooltip (.dragging-map-pin__tooltip)   s4 above the pin · hugs, s64–200 wide, min s40 high
│   ├── Text (.dragging-map-pin__text)        padding s8 / s12 · Subtitle over Title
│   │   ├── Subtitle (.dragging-map-pin__subtitle)   Caption · optional
│   │   └── Title (.dragging-map-pin__title)         Compact Body
│   ├── Slot end (.dragging-map-pin__slot-end)       chevron s24 · end padding s4 · optional
│   └── Loader (.dragging-map-pin__loader)           s20 · Address Loading only
├── Pin (.dragging-map-pin__pin)              the type's glyph on a squircle head s40, stick 2 × 18
├── Shadow (.dragging-map-pin__shadow)        on the ground under the stick · Standard / Drag / Loading / Error
└── Point (.dragging-map-pin__point)          the suggested pickup point under the head · the two Suggested states
```

| Part | Required | Description |
|---|---|---|
| **Root** | Required | `s40` wide and `s52` high — the pin with its stick. Its **bottom centre is the location**: the host places the root there and every state keeps it there. The tooltip, the shadow and the point overflow the root |
| **Address Tooltip** | Optional | Figma `MapPinTooltip` (the set's `Address Tooltip` boolean, on by default): `s4` above the pin, centred on it. Three forms (the tooltip's Figma `State`) — Standard (Figma `Default`: Subtitle, Title, Slot end), Loading (the Loader alone), Not found (the Title alone, "Address not found"). Hidden in Drag |
| **Text** | Required in Standard and Not found | Padding `s8` top / bottom, `s12` left / right; at least `s64` and at most 200 wide — longer text wraps |
| **Subtitle** | Optional | What the pin is — "Pickup point" (Point A), "Destination" (Point B); the host may change it ("You can change this text") |
| **Title** | Required | The address. Not found: "Address not found". Figma's `MapPinTooltip` has a `Title` boolean; a tooltip without the address names nothing, so the web always draws it — the Loading form replaces the whole Text |
| **Slot end** | Optional | A `chevron-right`, `s24`, with `s4` after it — the tooltip leads on (to the address search). Off in Not found |
| **Loader** | Address Loading | `[Loader] S (20) / Style=Primary` — a 20 ring in `TextAndIcon/Primary` |
| **Pin** | Required | The type's icon — `onmap-a-pin-color` (Point A) or `onmap-b-pin-color` (Point B): a squircle head `s40` (a 2 `TextAndIcon/InversePrimary` ring round the head fill) with the glyph, over a 2 × 18 stick. In Error the head and stick turn `TextAndIcon/Warning` and the glyph gives way to `warning-filled`. In the Suggested states the stick is not drawn |
| **Shadow** | Standard, Drag, Loading, Error | A blurred ellipse on the ground, centred on the location: `s20 × s8` at rest, `s40 × s12` in Drag |
| **Point** | The Suggested states | The suggested pickup point the pin sits on, centred on the location, `s4` under the head: `onmap-point-color` (20) when chosen, `onmap-point-selected-color` (24) while the pin hovers it |

- The Figma variants differ in height (56 / 68 / 64 / 68) because Figma grows the frame downward; on the
  web the location is fixed and the parts move around it — the pin lifts, the shadow and the point sit on
  the location.
- The pin, the shadow and the point are artwork: the icons' own paths, their fills bound to the tokens
  Figma binds.

### RTL layout

`RTL = on` mirrors the **tooltip only** (Figma `MapPinTooltip` `RTL=true`): the Subtitle and the Title
align right, the Slot end moves to the left and the chevron points left. The pin, the shadow and the point
are not mirrored ("Don't mirror other map elements including MapPin and Suggested pickup point"). On the
web the root's direction (`dir="rtl"` on it or an ancestor) does both.

---

## 3. Variants and sizes

| Axis | Values | Default | Web mechanism |
|---|---|---|---|
| **Type** | `point-a-rh` · `point-a-courier` · `point-b` | `point-a-rh` (Figma's default) | The glyph inside the pin (Point A / Point B); `.dragging-map-pin--point-b` for Point B's head fill. `data-ds-preset` |
| **State** | `standard` · `drag` · `hover-suggested-pickup` · `selected-suggested-pickup` · `loading` · `error` | `standard` | See § 4. `.dragging-map-pin--{state}` (the bare root is Standard). `data-ds-state` |
| **Address Tooltip** | `on` · `off` | `on` | The `.dragging-map-pin__tooltip` element, present or not |
| **Subtitle** | `on` · `off` | `on` | The `.dragging-map-pin__subtitle` element. Text: the host's |
| **Title** | the address (text) | — | The `.dragging-map-pin__title` element, always present in the Standard and Not found forms (§ 2). Text: the host's |
| **Slot end** | `on` · `off` | `on` | The `.dragging-map-pin__slot-end` element |
| **RTL** | `off` · `on` | `off` | `dir="rtl"` on the root or an ancestor — the tooltip mirrors, the pin does not |

- One size. The pin is `s40` wide; the tooltip hugs its text.
- Point A (Courier) equals Point A (RH) in Figma — the value exists so a courier screen can say so (and
  analytics can tell them apart); it changes nothing on the screen until Figma gives it its own glyph.

---

## 4. States

The Specification frame: "PointA has six states"; Point B the same six.

| State | Figma | What shows |
|---|---|---|
| **Standard** | Selected | The pin at rest on the location, the small shadow under the stick, the Standard tooltip with the address. "The initial state when the user opens the map screen. The app automatically determines the user's current location and displays their actual address" |
| **Drag** | Drag | The map is moving: the pin lifts `s10` — the stick ends `s4` above the ground — and the shadow grows to `s40 × s12` and lightens. No tooltip ("The Address Tooltip is hidden") |
| **Hover Suggested Pickup** | Hover Suggested Pickup | The pin is over a suggested pickup point: no stick, no shadow; the head sits `s4` above the point, which grows to its 24 hover form (a soft outer ring). The Standard tooltip |
| **Selected Suggested Pickup** | Selected Suggested Pickup | The pin rests on a chosen suggested point: the point back at 20, the head `s4` above it. The Standard tooltip. (The other suggested points turn transparent — they are `MapPinSuggestedPickup`, not this component) |
| **Address Loading** | Address Loading | The pin at rest, the Loading tooltip — the Loader in place of the text |
| **Error** | Error (address not found) | The head and the stick in `TextAndIcon/Warning`, the `warning-filled` glyph; the Not found tooltip, "Address not found" |

- The Figma name of the rest state is "Selected" (the pin is set on an address); the DS name is
  **Standard**.
- Figma flags:
  - The frame's text says Address Loading hides the tooltip; the variants show the Loading tooltip — the
    web follows the variants.
  - Point B's Address Loading and Error variants use the Point A pin (`onmap-a-pin-color`) — the web
    draws Point B's own pin.
  - Point A (Courier) is the RH pin in every state (§ 1).

---

## 5. Animation and behavior

Reference: [`motion-rules.md`](https://super-dollop-pzmo65r.pages.github.io/motion.md). The Specification frame has no
Animation block; the movements come from its States text ("it moves up and the shadow under the Pin
increases in size", "it increases in size", "returns to its original size") and the tokens are the DS's
choice — accepted as the DS contract (owner, 2026-11-12).

| Event | Pattern | Token | Notes |
|---|---|---|---|
| The pin lifts (→ Drag) | `Patterns/Position Short` appear | `var(--pattern-position-short-appear)` | `transform` on the pin, `s10` up (150ms, slow-ease-out); the shadow grows and lightens on the same token (`transform`, `opacity`) |
| The pin sets down (Drag →) | `Patterns/Position Short` hide | `var(--pattern-position-short-hide)` | The pin, the shadow back (150ms, accelerated-ease-in) |
| The tooltip shows | `Transforming/Enter/Fast` | `var(--transforming-enter-fast)` | `opacity` — the catalogue's tooltip appearance (150ms); its content's `visibility` rides the same token |
| The tooltip hides (→ Drag) | `Transforming/Exit/Fast` | `var(--transforming-exit-fast)` | `opacity` — the catalogue's tooltip hide (100ms); its content turns `visibility: hidden` when the fade ends — the tooltip, the live region, stays |
| The point grows / shrinks (Hover ↔ Selected Suggested) | `Patterns/Scale Short` | `var(--pattern-scale-short-appear)` / `var(--pattern-scale-short-hide)` | The 20 point and the 24 hover point cross on `opacity` (100ms in, 200ms back); the head and the tooltip rise `s4` / fall to `s2` on the same tokens (`transform`) |
| The Loader | `Patterns/Spin` | `var(--pattern-spin)` | The `[Loader]` rotation, as on Button — one turn in 800ms |

- **The shadow grows on the compositor.** Its 20 × 8 box scales about the location (× 2 wide, × 1.5
  high — `s40 / s20`, `s12 / s8`) rather than resizing, so the motion stays on `transform`. Its 4 layer
  blur scales with it in Drag — softer than Figma's — accepted as the DS contract (owner, 2026-11-12).
- **No motion:** the stick and the shadow on a suggested point, the Error colour and glyph, and the
  tooltip's content (the Loader, the address, "Address not found") — they change at once, and the
  tooltip takes its new width at once (width does not animate).

- **Behavior.** The pin has no input of its own: the map takes the drag, the pin follows the map's
  state. The host sets Drag when the map starts moving, Address Loading when it stops and asks for the
  address, then Standard (the address in the Title) or Error. Over a suggested pickup point the host
  sets Hover Suggested Pickup; when the pin is set on one, Selected Suggested Pickup.
- **Location.** The root's bottom centre stays on the location in every state; only the parts move.
- **Reduced motion.** Every token above is zeroed at the token — the pin jumps, the tooltip appears at
  once, the Loader holds still.

---

## 6. Color tokens

| Element | Token | Notes |
|---|---|---|
| Head fill — Point A | `var(--text-and-icon-primary)` | Every state but Error |
| Head fill — Point B | `var(--background-inverse-primary)` | Standard, Drag, Loading — Figma's `onmap-b-pin-color` binds Background; in the Suggested states the head is `TextAndIcon/Primary` (`onmap-b-pin-passenger-android-color`) |
| Head ring | `var(--text-and-icon-inverse-primary)` | The 2 ring round the head |
| Glyph | `var(--text-and-icon-inverse-primary)` | The passenger / the flag; the warning glyph in Error |
| Stick | `var(--text-and-icon-primary)` | |
| Head and stick — Error | `var(--text-and-icon-warning)` | "TextAndIcon/Warning — pin color in error state" |
| Shadow | `var(--text-and-icon-always-dark)` | At the layer opacity Figma sets — 12% at rest, 8% in Drag — and a 4 blur (§ 8) |
| Point | `var(--text-and-icon-primary)` + `var(--text-and-icon-inverse-primary)` | The ring and its centre; the hover point's outer ring is the icon's own 50% |
| Tooltip fill | `var(--background-base-white)` | Black in Night |
| Tooltip edge | `var(--border-transparent)` | A 1 ring outside the tooltip |
| Subtitle | `var(--text-and-icon-secondary)` | |
| Title | `var(--text-and-icon-primary)` | |
| Chevron | `var(--text-and-icon-primary)` | |
| Loader | `var(--text-and-icon-primary)` | `Style=Primary` |

- **The shadow is the one sanctioned layer opacity** (owner, 2026-11-03): Figma draws it as an
  `AlwaysDark` ellipse at 12% / 8% layer opacity with a 4 layer blur, and no DS token holds a
  translucent blurred ellipse. The web mirrors the layer values, held once in the component's block —
  the Squircle Img precedent. Flagged for a future token.

---

## 7. Typography

| Element | Style | Tokens | Scaling |
|---|---|---|---|
| Subtitle | Caption/Caption — 12/16, Suisse Intl Book | `var(--text-caption-caption-*)` | `full` (Figma binds `SP/150% ratio`) |
| Title | Body/Compact Body — 14/16, Suisse Intl Book | `var(--text-body-compact-body-*)` | `full` (Figma binds `SP/150% ratio`) |

- "Only the Title and Subtitle in the Tooltip increase in size" (the frame's Resizing block): the text
  grows to 150% and the tooltip grows with it, wrapping at its 200 maximum. Everything else keeps its
  size — the spacing tokens follow the SP family Figma binds (§ 8): the pin and the tooltip's box are
  fixed (`--sp-100-sN`), the chevron grows with the text, the Loader is capped at 130%.
- The text is aligned to the start — left in LTR, right in RTL.

---

## 8. Spacing

Each value takes the SP family Figma binds (`token-usage`): `SP/100% ratio` → `var(--sp-100-sN)` (fixed),
`SP/130% ratio` → `var(--sp-130-sN)`, `SP/150% ratio` → `var(--sp-sN)` (full). The pin's geometry is not
bound in Figma — it is icon artwork, and "only the Title and Subtitle increase in size" — so it is fixed.

| Property | Token | @100% | Figma | Notes |
|---|---|---|---|---|
| Pin — width / height | `var(--sp-100-s40)` / `var(--sp-100-s52)` | 40 / 52 | unbound (icon) | The head `s40`, the stick to 52 — the root |
| Tooltip ↔ pin | `var(--sp-100-s4)` | 4 | unbound | "spacing between Tooltip and Pin in selected state" |
| Lift in Drag | `var(--sp-100-s10)` | 10 | unbound | The stick ends `s4` above the shadow — "spacing between Pin and shadow in drag state" |
| Head ↔ point (Suggested) | `var(--sp-100-s4)` | 4 | unbound | "spacing between Pin and Suggested Pickup Point in selected state"; the head rises `var(--sp-100-s2)` (Selected) / `var(--sp-100-s4)` (Hover) so the point centres on the location |
| Shadow — rest | `var(--sp-100-s20)` × `var(--sp-100-s8)` | 20 × 8 | unbound | Centred on the location |
| Shadow — Drag | `var(--sp-100-s40)` × `var(--sp-100-s12)` | 40 × 12 | unbound | Centred on the location; on the web the rest shadow scaled (§ 5) |
| Shadow — blur / opacity | — | 4 / 12% · 8% | layer values | Figma's layer values (§ 6) — the component's own custom properties, not tokens |
| Tooltip — min width | `var(--sp-100-s64)` | 64 | unbound | On the Text |
| Tooltip — max width | `calc(var(--sp-100-s100) + var(--sp-100-s100))` | 200 | unbound | On the Text — the scale has no `s200` (the Card precedent); longer text wraps |
| Tooltip — min height | `var(--sp-100-s40)` | 40 | `SP/100% ratio/s40` | |
| Text padding | `var(--sp-100-s8)` / `var(--sp-100-s12)` | 8 / 12 | `SP/100% ratio` | Top-bottom / left-right |
| Slot end — chevron | `var(--sp-s24)` | 24 | `SP/150% ratio/s24` | Grows with the text |
| Slot end — end padding | `var(--sp-100-s4)` | 4 | `SP/100% ratio/s4` | The padding after the chevron, to the edge |
| Tooltip radius | `var(--sp-100-s16)` | 16 | `SP/100% ratio/s16` | The frame's Tooltip list says "s4 — corner radius" and its note "s16 (max*130%)"; the component's binding wins — flagged |
| Tooltip edge | — | 1 | — | Outside the box, the DS hairline (no SP `s1`) |
| Loader | `var(--sp-130-s20)` | 20 | `SP/130% ratio/s20` | `[Loader] S` |

---

## 9. Usage context

### When to use DraggingMapPin

- To set a pickup point by moving the map — the first map screen ("Where to?"), the courier order —
  Point A.
- To set a destination on the map — Point B.
- To show a suggested pickup point the pin can set on — the Suggested states.

### When not to use

- To mark a point that does not move — a placed route's start, end or stopovers: the on-route pins
  (`OnRouteMapPointA` / `PointB` / `Stopover`, Figma-only).
- For a point the person drags by itself — the pin stays at the centre and the map moves.
- With a long text in the tooltip — it is the address; a longer explanation belongs in the sheet under
  the map.

### Related components

- [Button](https://super-dollop-pzmo65r.pages.github.io/button.md) — shares the Loader technique.
- [Notification](https://super-dollop-pzmo65r.pages.github.io/notification.md) — an error that needs more than "Address not found".

---

## 10. Accessibility

Brief summary. Full spec: [`dragging-map-pin-a11y.md`](https://super-dollop-pzmo65r.pages.github.io/dragging-map-pin.md).

- **Only the tooltip speaks** — "Pickup point, Botanical Garden": the Subtitle, then the Title, as text.
  The pin, the shadow, the point and the chevron are hidden.
- **The address is announced when it changes** — on the web the tooltip is a polite live region, so a new
  address or "Address not found" is read when the pin sets down (a DS addition; Figma covers only the
  Standard state).
- **Not colour alone** — Error is the orange pin, the warning glyph and the words "Address not found".
- **Text scaling** — the Title and the Subtitle scale to 150%.
- **The chevron** suggests the tooltip leads on, yet Figma gives it the text role — flagged.

---

## 11. Analytics and coverage contract

Contract per [`prototype-analytics-and-coverage.md`](https://github.com/inDriver/oymyakon-ds/blob/main/docs/prototype-analytics-and-coverage.md).

| Field | Value |
|---|---|
| `data-ds-component` | `dragging-map-pin` |
| Coverage unit | yes — one root, one DS component instance |
| Tap target model | none — the map takes the drag; the tooltip, when the host makes it lead on, is the host's target |
| Actions | none |
| Internal targets | none |
| Emits value | no — the address is the host's |

Attribute model: `data-ds-preset` = the type (`point-a-rh`, `point-a-courier`, `point-b`);
`data-ds-state` = `standard`, `drag`, `hover-suggested-pickup`, `selected-suggested-pickup`, `loading` or
`error`. RTL is the reading direction, not a state.

```html
<!-- Point A (RH), Standard — the tooltip names the address; the root's bottom centre is the location -->
<div class="dragging-map-pin"
     data-ds-component="dragging-map-pin" data-ds-component-id="ride.pickup-pin"
     data-ds-preset="point-a-rh" data-ds-state="standard">
  <div class="dragging-map-pin__tooltip" role="status">
    <div class="dragging-map-pin__text">
      <span class="dragging-map-pin__subtitle">Pickup point</span><span class="visually-hidden">, </span>
      <span class="dragging-map-pin__title">Botanical Garden</span>
    </div>
    <span class="dragging-map-pin__slot-end" aria-hidden="true"><svg …>chevron-right</svg></span>
  </div>
  <svg class="dragging-map-pin__pin" viewBox="0 0 40 52" aria-hidden="true">…onmap-a-pin-color…</svg>
  <span class="dragging-map-pin__shadow" aria-hidden="true"></span>
</div>

<!-- Address Loading — the Loader in place of the text; the live region stays, busy -->
<div class="dragging-map-pin dragging-map-pin--loading" … data-ds-state="loading">
  <div class="dragging-map-pin__tooltip" role="status" aria-busy="true">
    <span class="dragging-map-pin__loader" aria-hidden="true"></span>
  </div>
  …
</div>

<!-- Error — the warning glyph and the Not found text -->
<div class="dragging-map-pin dragging-map-pin--error" … data-ds-state="error">
  <div class="dragging-map-pin__tooltip" role="status">
    <div class="dragging-map-pin__text"><span class="dragging-map-pin__title">Address not found</span></div>
  </div>
  …
</div>
```

The host places the root with its bottom centre on the location (for a map overlay: the root's
`translate` by half its width and its full height). Drag hides the tooltip through CSS
(the tooltip's `opacity`, its content's `visibility: hidden` — the region itself stays in the
accessibility tree); the host swaps the tooltip's content for Loading and Error and keeps the same
tooltip element, so the live region announces the change.

---

## Changelog

| Version | Date | Change |
|---|---|---|
| 3.0.1 | 2026-11-12 | Owner ruling and Status Draft → Ready (owner). The DS's choices are accepted as the contract: the motion tokens read from the frame's States text, the shadow scaling with its blur on the compositor, and the Loading / Error / new-address announcements. The courier glyph and the other Figma-side flags stay open. |
| 3.0.0 | 2026-11-03 | Initial spec, from the Figma `[MapPin] 3.0` page (page `14794:3465`): the `DraggingMapPin` set `15453:8077` (Type Point A (RH) / Point A (Courier) / Point B × State Selected / Drag / Hover Suggested Pickup / Selected Suggested Pickup / Address Loading / Error (address not found) + the `Address Tooltip` boolean), its `MapPinTooltip` set `15459:8603` (Default / Not found / Loading × RTL) and the Specification frame `15619:16046`. Web: one root whose bottom centre is the location — the tooltip `s4` above, the pin lifting `s10` in Drag, the shadow and the suggested point centred on the location; the tooltip mirrors in RTL, the pin does not. Courier = the RH pin, as in Figma (owner); the shadow mirrors Figma's layer opacity and blur (owner). Spacing takes the SP family Figma binds (Spacing 3.3.0): the pin and the tooltip's box fixed (`--sp-100-sN`), the chevron full, the Loader cap130. Motion on named tokens — the lift, the tooltip fade (the live region stays in the tree), the point crossfade, the Loader spin. |
</content>
</invoke>

---

# DraggingMapPin — Accessibility

DraggingMapPin is a picture on a map with a line of text above it. **Only the tooltip speaks** (the
Specification frame: "The screen reader should announce only the Title and Subtitle. Other elements
should be ignored"):

- **The tooltip** is text — the Subtitle, then the Title: "Pickup point, Botanical Garden".
- **The pin, the shadow, the suggested point and the chevron** are hidden. The pin's glyph (passenger /
  flag) repeats the Subtitle; the chevron is a picture of "leads on".
- **The pin is not a control.** It does not take focus or a gesture; the map does. Moving the map — by
  gesture, keyboard or the platform's map controls — is the host's.

| Visible | Announced |
|---|---|
| Pickup point · Botanical Garden (Point A, Standard) | "Pickup point, Botanical Garden" |
| Destination · Mall of Asia (Point B, Standard) | "Destination, Mall of Asia" |
| The Loader (Address Loading) | Nothing on its own — the address is announced when it arrives |
| Address not found (Error) | "Address not found" |
| The lifted pin, no tooltip (Drag) | Nothing |

The Specification frame covers the Standard state only. The announcements of Loading, Error and the new
address after a drag are the DS's addition — accepted as the DS contract (owner, 2026-11-12).

---

## Android · TalkBack

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| Tooltip | `contentDescription` "[Subtitle], [Title]" | — | — (plain text, no role) | — |
| Tooltip — after a drag | the same | — | `liveRegion = Polite` — the new text is read when it changes | — |
| Pin, shadow, point, chevron | — (`importantForAccessibility = no` / `clearAndSetSemantics {}`) | | | |

Voiced preview (the frame): "Pickup point, Botanical Garden".

### Edge states

- **Drag:** the tooltip is gone; nothing is announced while the map moves.
- **Address Loading:** the Loader is not announced; the polite region reads the address when it arrives.
- **Error:** "Address not found" is read through the same region.
- **Suggested states:** the tooltip reads as in Standard; the point is not announced.
- **RTL:** the tooltip reads in the language's order; the pin is not mirrored and not announced.
- **Text scaling:** the Subtitle and the Title scale to 150%; the tooltip grows and wraps.

---

## iOS · VoiceOver

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| Tooltip | "[Subtitle], [Title]" | — | `.staticText` (Trait: text) | — |
| Tooltip — after a drag | the same | — | `UIAccessibility.post(notification: .announcement)` with the new text | — |
| Pin, shadow, point, chevron | — `accessibilityHidden(true)` | | | |

Voiced preview (the frame): "Pickup point, Botanical Garden".

### Edge states

- **Drag / Address Loading:** nothing is announced; the address, or "Address not found", is announced
  when it arrives.
- **RTL / Dynamic Type:** as on Android.

---

## Web preview (reference)

- **The tooltip** is a `role="status"` element — a polite live region, read in browse mode as text and
  re-read when its content changes. The Subtitle and the Title are separated by a `.visually-hidden`
  ", " so the two never run together ("Pickup pointBotanical Garden").
- **Address Loading:** the region stays in the page with `aria-busy="true"` and the Loader
  `aria-hidden`; the host clears `aria-busy` as it puts the address in, and the region reads it once.
- **Error:** the region holds "Address not found".
- **Drag:** the tooltip fades out on `opacity` and its content goes `visibility: hidden` (out of the
  accessibility tree), so the region says nothing while the map moves. The region itself stays in the
  tree, empty: a live region that is added, or comes back from hidden, at the moment its text changes is
  treated as new and not announced. For the same reason the host keeps the same tooltip element across
  states and swaps only its content.
- **Hidden parts:** the pin `<svg>`, the shadow, the point and the chevron are `aria-hidden="true"`.
- **No focus stop.** The pin is not focusable and takes no key. Keyboard users move the map through the
  host's map controls; the tooltip announces the address the pin lands on.
- **The chevron** (Slot end) pictures that the tooltip leads on — to the address search on the platforms.
  Figma gives the tooltip the text role. When the host makes the tooltip a target, a `<button>` named by
  the same text goes inside the status region, which stays mounted and keeps announcing; its target is at
  least `s44` — the tooltip's minimum is `s40`, so the button's hit area reaches past it (inset padding or
  a `::before`) — flagged for the owner.
- **Forced colours:** the pin and the point are artwork and keep their fills (`forced-color-adjust: none`
  — forced, every path would turn `CanvasText` and the glyph would vanish into the head); the tooltip draws
  a `CanvasText` outline in place of its fill edge.
- **RTL:** `dir="rtl"` mirrors the tooltip only.
- **Reduced motion:** the lift, the tooltip fade and the point's growth are zeroed at the token; the Loader
  holds still.

### Contrast

- Title — `TextAndIcon/Primary` on `Background/BaseWhite`: 17.76:1 Day, 21:1 Night.
- Subtitle — `TextAndIcon/Secondary` on `Background/BaseWhite`: 5.25:1 Day, 9.75:1 Night.
- Error glyph — `TextAndIcon/InversePrimary` on `TextAndIcon/Warning`: 3.15:1 Day, 6.70:1 Night (a
  graphic, 3:1 floor). The state is also the words "Address not found" — never the colour alone.
- The pin against the map — the 2 `TextAndIcon/InversePrimary` ring keeps the head apart from any map
  colour.

---

## Checklist

- [ ] Only the tooltip is announced — "[Subtitle], [Title]", as text
- [ ] The pin, the shadow, the point and the chevron are hidden from assistive tech
- [ ] The new address, and "Address not found", are announced once when the pin sets down (polite)
- [ ] Nothing is announced during Drag or Address Loading
- [ ] The Subtitle and the Title scale to 150%
- [ ] The tooltip mirrors in RTL; the pin does not
- [ ] Error is told by the glyph and the text, not by the colour alone
</content>

---

## Machine contract — `specs/components/dragging-map-pin/capabilities.json`

Axes, allowed values, defaults, constraints and CSS/Figma bindings. Read this instead of guessing what the component can do.

```json
{
  "$schema": "../component-capabilities.schema.json",
  "id": "dragging-map-pin",
  "name": "DraggingMapPin",
  "version": "3.0.1",
  "description": "The pin a person sets an address with by dragging the map under it: a squircle head (the type's glyph) on a stick, a ground shadow and an Address Tooltip above (Subtitle + Title + a chevron slot). Types Point A (RH) · Point A (Courier, the RH pin as in Figma) · Point B; States Standard · Drag · Hover / Selected Suggested Pickup · Address Loading · Error. The root's bottom centre is the location in every state.",
  "files": {
    "spec": "specs/components/dragging-map-pin/dragging-map-pin.md",
    "a11y": "specs/components/dragging-map-pin/dragging-map-pin-a11y.md",
    "preview": "src/dragging-map-pin.njk",
    "css": "src/shared/shared.css"
  },
  "figma": {
    "library": "🕹️ Oymyakon (components) — main file",
    "fileKey": "7vdl5YkZFDWvh9QvSmydsH",
    "componentSets": {
      "DraggingMapPin": {
        "key": "c874af927973c390c0ae60e7ea3b58499d88df89",
        "note": "Set 15453:8077 on page [MapPin] 3.0 (14794:3465): Type (Point A (RH) · Point A (Courier) · Point B) × State (Selected · Drag · Hover Suggested Pickup · Selected Suggested Pickup · Address Loading · Error (address not found)) + the BOOLEAN Address Tooltip#15459:12 (default on). Pins are the icons onmap-a-pin-color (both Point A types) / onmap-b-pin-color; the Suggested states use onmap-a-passenger-android-color / onmap-b-pin-passenger-android-color with onmap-point-color / onmap-point-selected-color. Point B's Address Loading and Error variants use the Point A pin — a Figma slip."
      },
      "MapPinTooltip": {
        "key": "96580699d4d27ec411126ad1e4198a95d9cb5609",
        "note": "Set 15459:8603 in Building blocks (15526:22350): State (Default · Not found · Loading) × RTL; BOOLEAN Subtitle, Title, SlotEnd; TEXT Subtitle Text, Title Text. Radius bound SP s16, min height s40, Text min 64 / max 200, padding s8 / s12, edge Border/Transparent 1 outside, fill Background/BaseWhite. Folded into DraggingMapPin as its tooltip part."
      }
    },
    "capturedAt": "2026-11-03"
  },
  "root": {
    "class": "dragging-map-pin",
    "dataDsComponent": "dragging-map-pin"
  },
  "anatomy": {
    "tooltip": {
      "class": "dragging-map-pin__tooltip",
      "optional": true,
      "notes": "Figma MapPinTooltip, s4 above the pin and centred on it; role=\"status\" (a polite live region). Standard (Figma Default: Text + Slot end), Loading (the Loader), Not found (the Title alone). Hidden in Drag — opacity on the tooltip, visibility on its content; the region itself stays in the accessibility tree."
    },
    "text": {
      "class": "dragging-map-pin__text",
      "notes": "Padding s8 / s12; min width s64, max width 200 (calc(var(--sp-100-s100) + var(--sp-100-s100))); the Subtitle over the Title, aligned to the start."
    },
    "subtitle": {
      "class": "dragging-map-pin__subtitle",
      "optional": true,
      "notes": "Caption/Caption in TextAndIcon/Secondary; followed by a .visually-hidden \", \"."
    },
    "title": {
      "class": "dragging-map-pin__title",
      "notes": "Body/Compact Body in TextAndIcon/Primary — the address; \"Address not found\" in Error."
    },
    "slotEnd": {
      "class": "dragging-map-pin__slot-end",
      "optional": true,
      "notes": "chevron-right s24 with s4 end padding; aria-hidden; flips in RTL."
    },
    "loader": {
      "class": "dragging-map-pin__loader",
      "optional": true,
      "onlyWhen": { "state": ["loading"] },
      "notes": "[Loader] S (20) / Style=Primary — the Button technique on Patterns/Spin; aria-hidden."
    },
    "pin": {
      "class": "dragging-map-pin__pin",
      "notes": "The type's icon as inline SVG (40 × 52): .dragging-map-pin__stick, __ring, __head, __glyph and the Error __warning, fills bound to tokens in shared.css; aria-hidden."
    },
    "shadow": {
      "class": "dragging-map-pin__shadow",
      "optional": true,
      "onlyWhen": { "state": ["standard", "drag", "loading", "error"] },
      "notes": "AlwaysDark ellipse centred on the location: s20 × s8 at 12%, s40 × s12 at 8% in Drag; a 4 blur — Figma's layer values in --dragging-map-pin-shadow-*. Absent in the Suggested states."
    },
    "point": {
      "class": "dragging-map-pin__point",
      "optional": true,
      "onlyWhen": { "state": ["hover-suggested-pickup", "selected-suggested-pickup"] },
      "notes": "The suggested pickup point centred on the location: onmap-point-color (20, .dragging-map-pin__point-selected) and onmap-point-selected-color (24, .dragging-map-pin__point-hover) in one cell; the state shows one."
    }
  },
  "axes": {
    "type": {
      "title": "Type",
      "type": "enum",
      "values": [
        "point-a-rh",
        "point-a-courier",
        "point-b"
      ],
      "default": "point-a-rh",
      "css": {
        "mechanism": "The glyph inside the pin SVG (Point A / Point B); .dragging-map-pin--point-b gives Point B's head Background/InversePrimary. point-a-courier draws the RH pin, as Figma does. data-ds-preset."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Type",
        "values": {
          "point-a-rh": "Point A (RH)",
          "point-a-courier": "Point A (Courier)",
          "point-b": "Point B"
        }
      },
      "constraints": [
        "Point A (Courier) is the same pin as Point A (RH) until Figma gives it its own glyph (owner, 2026-11-03).",
        "The default Subtitle is \"Pickup point\" for Point A and \"Destination\" for Point B; the host may change it."
      ]
    },
    "state": {
      "title": "State",
      "type": "enum",
      "values": [
        "standard",
        "drag",
        "hover-suggested-pickup",
        "selected-suggested-pickup",
        "loading",
        "error"
      ],
      "default": "standard",
      "css": {
        "mechanism": "standard = the bare root; .dragging-map-pin--drag, --hover-suggested-pickup, --selected-suggested-pickup, --loading, --error. The host sets it from the map: drag while it moves, loading while it asks for the address, then standard or error. data-ds-state."
      },
      "figma": {
        "kind": "variant-property",
        "property": "State",
        "values": {
          "standard": "Selected",
          "drag": "Drag",
          "hover-suggested-pickup": "Hover Suggested Pickup",
          "selected-suggested-pickup": "Selected Suggested Pickup",
          "loading": "Address Loading",
          "error": "Error (address not found)"
        }
      },
      "constraints": [
        "The root's bottom centre is the location in every state; only the parts move.",
        "Drag hides the tooltip; Loading shows the Loader in it; Error shows \"Address not found\"."
      ]
    },
    "tooltip": {
      "title": "Address Tooltip",
      "type": "boolean",
      "default": true,
      "css": {
        "mechanism": "The .dragging-map-pin__tooltip element present or not."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Address Tooltip"
      }
    },
    "subtitle": {
      "title": "Subtitle",
      "type": "boolean",
      "default": true,
      "css": {
        "mechanism": "The .dragging-map-pin__subtitle element, present or not; its text is the host's — \"Pickup point\" (Point A) or \"Destination\" (Point B) by default."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Subtitle",
        "notes": "MapPinTooltip BOOLEAN Subtitle + TEXT Subtitle Text."
      }
    },
    "title": {
      "title": "Title",
      "type": "text",
      "default": "Address",
      "css": {
        "mechanism": "The .dragging-map-pin__title element, always present in the Standard and Not found forms; its text is the address."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Title",
        "notes": "MapPinTooltip BOOLEAN Title + TEXT Title Text. The web keeps the Title on: a tooltip without the address names nothing; the Loading form replaces the whole Text."
      }
    },
    "slotEnd": {
      "title": "Slot end",
      "type": "boolean",
      "default": true,
      "css": {
        "mechanism": "The .dragging-map-pin__slot-end element present or not."
      },
      "figma": {
        "kind": "variant-property",
        "property": "SlotEnd"
      }
    },
    "rtl": {
      "title": "RTL",
      "type": "boolean",
      "default": false,
      "css": {
        "mechanism": "dir=\"rtl\" on the root or an ancestor: the tooltip's text aligns right, the slot end moves left and the chevron flips; the pin, the shadow and the point are not mirrored."
      },
      "figma": {
        "kind": "variant-property",
        "property": "RTL",
        "notes": "On MapPinTooltip only — \"Don't mirror other map elements including MapPin and Suggested pickup point\"."
      }
    }
  },
  "states": {
    "default": [
      "standard",
      "drag",
      "hover-suggested-pickup",
      "selected-suggested-pickup",
      "loading",
      "error"
    ]
  },
  "constraints": [
    "The pin is not a control: no focus, no gesture — the map takes the drag and the host sets the state.",
    "Only the tooltip is announced — \"[Subtitle], [Title]\" as text; the pin, the shadow, the point and the chevron are aria-hidden.",
    "The tooltip is one role=\"status\" element kept across states and never hidden itself (Drag hides its content), so a new address or \"Address not found\" is announced once.",
    "Error is the TextAndIcon/Warning pin, the warning-filled glyph and the words — never the colour alone.",
    "The shadow's opacity and blur are Figma's layer values held in --dragging-map-pin-shadow-* (owner-sanctioned); no other raw value.",
    "Motion: the lift on Patterns/Position Short, the tooltip on Transforming/Enter|Exit/Fast, the point on Patterns/Scale Short, the Loader on Patterns/Spin — all zeroed under reduced motion."
  ],
  "analytics": {
    "dataDsComponent": "dragging-map-pin",
    "actions": [],
    "notes": "Coverage unit — one root. No tap target of its own: the map takes the drag. data-ds-preset = point-a-rh / point-a-courier / point-b; data-ds-state = standard / drag / hover-suggested-pickup / selected-suggested-pickup / loading / error."
  },
  "rtl": {
    "supported": true,
    "notes": "The tooltip mirrors (text to the right, the chevron slot to the left, the chevron flipped); the pin, the shadow and the point do not."
  }
}
```
