The map's zoom in, zoom out and centre buttons, and, when the screen handles them, a 3D toggle, a compass that turns the map back to north and a pause button for the map's motion, in one glass bar that is one tab stop, named and labelled in the reader's language.
When to use
Use MapControls for the map's own buttons, zoom in, zoom out and centre, and the 3D toggle, the compass and the pause when the screen handles them, once per map.
| Use instead | When |
|---|---|
| Toolbar | Another map action, such as a layers switch, needs a bar of its own beside it |
| IconButton | One icon action sits on its own, on a panel, a sheet or the top bar |
Kongens Nytorv
Metro M1, M2 and M3.
Variants and states
| Rest | Disabled | |
|---|---|---|
| Default |
- Rest
- Disabled
Props
| Name | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'vertical' | Which way the bar runs, and so which arrow keys move through it: Up and Down for vertical, the map's column at the bottom right; Left and Right for horizontal. |
disabled | boolean | false | Whether every button is disabled, such as while the map loads. The buttons stay focusable and in the arrow-key order. |
zoomInDisabled | boolean | false | Whether the zoom in button is disabled, at the map's closest zoom. It stays focusable and announces that it is dimmed, and a click fires no onZoomIn. |
zoomOutDisabled | boolean | false | Whether the zoom out button is disabled, at the map's farthest zoom. It stays focusable and announces that it is dimmed, and a click fires no onZoomOut. |
centerMapDisabled | boolean | false | Whether the centre button is disabled, such as before the map can be moved, while the pause button stays enabled. It stays focusable and announces that it is dimmed, and a click fires no onCenterMap. |
onZoomIn | (event: MouseEvent<HTMLButtonElement, MouseEvent>) => void | — | Called when the zoom in button is pressed, by click, Enter or Space, with the click event. The screen zooms the map in by one step. |
onZoomOut | (event: MouseEvent<HTMLButtonElement, MouseEvent>) => void | — | Called when the zoom out button is pressed, by click, Enter or Space, with the click event. The screen zooms the map out by one step. |
onCenterMap | (event: MouseEvent<HTMLButtonElement, MouseEvent>) => void | — | Called when the centre button is pressed, by click, Enter or Space, with the click event. The screen moves and zooms the map to show the whole network. |
paused | boolean | false | Whether the map's motion is paused, held by the screen. The pause button shows the play icon and is named "Resume" while it is true, and the pause icon and "Pause" while it is false. |
onPausedChange | (event: React.MouseEvent<HTMLButtonElement>, paused: boolean) => void | — | Called when the pause button is pressed, by click, Enter or Space, with the click event and the state the press asks for: true while the map moves, false while it is paused. Given, it adds the pause button after the centre button; the screen stops or starts the map's motion and passes the new paused. |
tilted | boolean | false | Whether the map is tilted into 3D, held by the screen, by the 3D toggle or a gesture alike. The 3D toggle is pressed, aria-pressed="true" on its on fill, while it is true. |
onTiltedChange | (event: React.MouseEvent<HTMLButtonElement>, tilted: boolean) => void | — | Called when the 3D toggle is pressed, by click, Enter or Space, with the click event and the state the press asks for: true while the map is flat, false while it is tilted. Given, it adds the 3D toggle after the centre button; the screen tilts the map or lays it flat and passes the new tilted. |
heading | number | 0 | The map's heading in degrees, its bearing clockwise from north, held by the screen. The compass's needle turns by minus heading, so it points to north on the map, and the compass is described as turned away from north while it is turned; a value that isn't finite points it up. |
onResetNorth | (event: MouseEvent<HTMLButtonElement, MouseEvent>) => void | — | Called when the compass is pressed, by click, Enter or Space, with the click event. Given, it adds the compass after the centre button, and after the 3D toggle when there is one; the screen turns the map back to north and passes the new heading. |
mapControlsLabel | string | The locale's | Overrides "Map controls", the bar's name. |
zoomInLabel | string | The locale's | Overrides "Zoom in", the zoom in button's name. |
zoomOutLabel | string | The locale's | Overrides "Zoom out", the zoom out button's name. |
centerMapLabel | string | The locale's | Overrides "Centre the map", the centre button's name. |
pauseLabel | string | The locale's | Overrides "Pause", the pause button's name while the map moves. |
resumeLabel | string | The locale's | Overrides "Resume", the pause button's name while the map is paused. |
view3dLabel | string | The locale's | Overrides "3D view", the 3D toggle's name. |
resetNorthLabel | string | The locale's | Overrides "Reset to north", the compass's name. |
turnedFromNorthLabel | string | The locale's | Overrides "Map turned away from north", the compass's description while the map is turned. |
className | string | — | Reaches the root, the role="toolbar" element. className is merged after pn-Toolbar and pn-MapControls. |
style | CSSProperties | — | Reaches the root, the role="toolbar" element. className is merged after pn-Toolbar and pn-MapControls. |
ref | Ref<HTMLDivElement> | — | Reaches the root, the role="toolbar" element. className is merged after pn-Toolbar and pn-MapControls. |
orientation'horizontal' | 'vertical', default'vertical'- Which way the bar runs, and so which arrow keys move through it: Up and Down for
vertical, the map's column at the bottom right; Left and Right forhorizontal. disabledboolean, defaultfalse- Whether every button is disabled, such as while the map loads. The buttons stay focusable and in the arrow-key order.
zoomInDisabledboolean, defaultfalse- Whether the zoom in button is disabled, at the map's closest zoom. It stays focusable and announces that it is dimmed, and a click fires no
onZoomIn. zoomOutDisabledboolean, defaultfalse- Whether the zoom out button is disabled, at the map's farthest zoom. It stays focusable and announces that it is dimmed, and a click fires no
onZoomOut. centerMapDisabledboolean, defaultfalse- Whether the centre button is disabled, such as before the map can be moved, while the pause button stays enabled. It stays focusable and announces that it is dimmed, and a click fires no
onCenterMap. onZoomIn(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void, default —- Called when the zoom in button is pressed, by click, Enter or Space, with the click event. The screen zooms the map in by one step.
onZoomOut(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void, default —- Called when the zoom out button is pressed, by click, Enter or Space, with the click event. The screen zooms the map out by one step.
onCenterMap(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void, default —- Called when the centre button is pressed, by click, Enter or Space, with the click event. The screen moves and zooms the map to show the whole network.
pausedboolean, defaultfalse- Whether the map's motion is paused, held by the screen. The pause button shows the
playicon and is named "Resume" while it istrue, and thepauseicon and "Pause" while it isfalse. onPausedChange(event: React.MouseEvent<HTMLButtonElement>, paused: boolean) => void, default —- Called when the pause button is pressed, by click, Enter or Space, with the click event and the state the press asks for:
truewhile the map moves,falsewhile it is paused. Given, it adds the pause button after the centre button; the screen stops or starts the map's motion and passes the newpaused. tiltedboolean, defaultfalse- Whether the map is tilted into 3D, held by the screen, by the 3D toggle or a gesture alike. The 3D toggle is pressed,
aria-pressed="true"on its on fill, while it istrue. onTiltedChange(event: React.MouseEvent<HTMLButtonElement>, tilted: boolean) => void, default —- Called when the 3D toggle is pressed, by click, Enter or Space, with the click event and the state the press asks for:
truewhile the map is flat,falsewhile it is tilted. Given, it adds the 3D toggle after the centre button; the screen tilts the map or lays it flat and passes the newtilted. headingnumber, default0- The map's heading in degrees, its bearing clockwise from north, held by the screen. The compass's needle turns by minus
heading, so it points to north on the map, and the compass is described as turned away from north while it is turned; a value that isn't finite points it up. onResetNorth(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void, default —- Called when the compass is pressed, by click, Enter or Space, with the click event. Given, it adds the compass after the centre button, and after the 3D toggle when there is one; the screen turns the map back to north and passes the new
heading. mapControlsLabelstring, default The locale's- Overrides "Map controls", the bar's name.
zoomInLabelstring, default The locale's- Overrides "Zoom in", the zoom in button's name.
zoomOutLabelstring, default The locale's- Overrides "Zoom out", the zoom out button's name.
centerMapLabelstring, default The locale's- Overrides "Centre the map", the centre button's name.
pauseLabelstring, default The locale's- Overrides "Pause", the pause button's name while the map moves.
resumeLabelstring, default The locale's- Overrides "Resume", the pause button's name while the map is paused.
view3dLabelstring, default The locale's- Overrides "3D view", the 3D toggle's name.
resetNorthLabelstring, default The locale's- Overrides "Reset to north", the compass's name.
turnedFromNorthLabelstring, default The locale's- Overrides "Map turned away from north", the compass's description while the map is turned.
classNamestring, default —- Reaches the root, the
role="toolbar"element.classNameis merged afterpn-Toolbarandpn-MapControls. styleCSSProperties, default —- Reaches the root, the
role="toolbar"element.classNameis merged afterpn-Toolbarandpn-MapControls. refRef<HTMLDivElement>, default —- Reaches the root, the
role="toolbar"element.classNameis merged afterpn-Toolbarandpn-MapControls.
Use it
import { MapControls } from '@mylder/perron'Ask your agent
search_docs({ query: "MapControls" })Keyboard and ARIA
From the Base UI reference for Toolbar.Root, Toolbar.Button and Toolbar.Separator, as the Toolbar spec gives them. "From" says who does it.
| Key | Does | From |
|---|---|---|
Tab, Shift+Tab | Moves focus into the bar, to the button that last had it (zoom in at first), and out again: the bar is one tab stop | Base UI |
ArrowDown, ArrowUp | In the vertical bar, the default, move focus to the next and the previous button, skipping the separators, wrapping while loopFocus is on: zoom in, zoom out, centre, then the 3D toggle, the compass and pause, each when it is there | Base UI |
ArrowRight, ArrowLeft | In a horizontal bar, move focus to the next and the previous button, in the same order, wrapping while loopFocus is on | Base UI |
Enter, Space | Press the focused button: call its onZoomIn, onZoomOut, onCenterMap or onResetNorth, or onTiltedChange or onPausedChange with the state it asks for, unless it is disabled; focus stays on the button as the toggle's aria-pressed, the compass's needle or the pause button's icon and name change | The browser |
Home and End do nothing, as on Toolbar. + and − zoom the map from the map canvas (Accessibility), not from these controls.
MapControls listens to no keys of its own and sets no aria-keyshortcuts, leaving + and − to the map. Accessibility gives those keys to the screen ("+ and − zoom, arrow keys pan"), the map canvas has focus when they are pressed, and the controls can't know that the screen binds them, so announcing a shortcut could promise one that does nothing.
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | toolbar, from Base UI | aria-label, the mapControls owned string, or the caller's aria-labelledby | aria-orientation |
zoomIn | button, native | aria-label, the zoomIn owned string | aria-disabled when zoomInDisabled or disabled |
zoomOut | button, native | aria-label, the zoomOut owned string | aria-disabled when zoomOutDisabled or disabled |
centerMap | button, native | aria-label, the centerMap owned string | aria-disabled when centerMapDisabled or disabled |
tilt | button, native, a toggle button by its aria-pressed | aria-label, the view3d owned string, the same while tilted and flat | aria-pressed, true while tilted and false while not; aria-disabled when disabled |
compass | button, native | aria-label, the resetNorth owned string, at every heading | Its description, the turnedFromNorth owned string in turned, through aria-describedby, while the map is turned; aria-disabled when disabled; no aria-pressed |
pause | button, native | aria-label, the pause owned string while the map moves, resume while paused | aria-disabled when disabled; no aria-pressed |
separator, angleSeparator, pauseSeparator | separator, from Base UI | none | aria-orientation, across the bar's |
icon | none: inside aria-hidden | none | none |
turned | none: text out of sight, the compass's description | none | none |
The pause button swaps its name, "Pause" or "Resume", and sets no aria-pressed. The APG's button pattern gives a two-state button one of two forms: a toggle whose label stays the same while aria-pressed says its state, such as "Mute", pressed or not; or a button whose label changes with the state, such as Play and Pause, which then has no aria-pressed, since "Pause, pressed" says the opposite of what it means. The icon swaps between pause and play, each naming the action a press takes, and the contract asks that everything the eye is told the ear is told, so the name swaps with the icon: a fixed "Pause" with aria-pressed would be heard as "Pause, pressed" while the eye sees play. The name says what the press does, as zoom in and zoom out do, and it is the name Mylder's map already gives the button. A screen reader may not announce the new name on the press itself; the vehicles stopping or moving is the press's feedback, and the name is read the next time focus reaches the button.
The 3D toggle takes the APG's other form: its name, "3D view", stays the same while aria-pressed says whether the map is tilted, heard as "3D view, toggle button, pressed" or "not pressed". Its icon, the cube, stays the same too, and its on fill is what the eye is told. The state is the map's, which a gesture changes as well as a press, so it is a state to announce rather than an action to name, as "Pause" is: a name that swapped, "Tilt the map" and "Lay the map flat", would leave the ear without the map's state once a gesture had tilted it. aria-pressed follows tilted, so a gesture the screen passes on is heard the next time focus reaches the toggle.
The compass's name says what a press does, "Reset to north", at every heading, as the zoom and centre buttons name theirs, and it sets no aria-pressed: the press is an action, not a state.
The compass tells the ear what its needle tells the eye: whether the map is turned away from north. While the needle is drawn turned, a finite heading that isn't a whole turn from north, the compass is described by the turnedFromNorth owned string, "Map turned away from north", through aria-describedby pointing at a VisuallyHidden span after it, the contract's part for words the eye gets from an icon, heard after its name as "Reset to north, button, Map turned away from north"; at north, and for a heading that isn't finite, which draws the needle unturned, the span and the aria-describedby are gone. aria-describedby is ARIA 1.0, which every screen reader reads, where ARIA 1.3's aria-description is not yet read evenly. The description changes only between north and turned, never at each degree, so a turn the screen passes frame by frame doesn't change what is heard until the map reaches north, where a screen reader would repeat or drop a number that changed at every frame; the name stays "Reset to north", what a press does, at every heading. The exact angle stays the map's to describe, as the zoom is: the needle's angle tells the eye no number either, only which way north lies, and the ear learns that north is not up.
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: each icon is
aria-hiddeninside its button, and the button'saria-labelsays the action it shows ("Zoom in", "Zoom out", "Centre the map", "Reset to north", and "Pause" or "Resume" as thepauseorplayicon shows), or, for the 3D toggle, the view it turns on, "3D view"; the toggle's on fill is itsaria-pressed; the compass's turned needle is its description, "Map turned away from north", througharia-describedbyandVisuallyHidden, whileheadingturns it, and none at north, as the paragraph above says; a dimmed button is announced througharia-disabled; the separators are announced as such. - Every landmark and named region has an accessible name: the bar is a
toolbarnamed "Map controls" by its owned string, so it tells itself apart from the top bar's toolbars. - Every default label can be overridden:
mapControlsLabel,zoomInLabel,zoomOutLabel,centerMapLabel,view3dLabel,resetNorthLabel,turnedFromNorthLabel,pauseLabelandresumeLabel. - Every shape the type accepts renders, in every variant: the props take no
ReactNode; both orientations, every…Disabledcombination anddisabledrender all three buttons, square, in the same order, and the 3D toggle in either state oftilted, the compass at anyheading, 0, negative, past 360 or not finite, and the pause button in either state ofpausedafter them when their handlers are given. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: none here; the controls keep no hidden copy and collapse nothing. - Native semantics first: native
<button>s fromToolbar.Button, Base UI'stoolbarandseparatorroles, with ARIA only for the names an icon can't give. - Focus moves in reading order and returns on close: the arrow keys move zoom in, zoom out, centre, then the 3D toggle, the compass and pause, in document order; the controls open nothing; a button that hits a zoom limit stays focusable, the 3D toggle stays the same element as
tiltedchanges, the compass stays in the bar at north, and the pause button stays the same element as its icon and name swap, so focus never jumps away from the key a person pressed. - Every target is at least
size.target-minsquare: each button issize.control(34 px) square. - Loops stop when the system asks for less motion: none here; the controls have no loop and no transition, and the compass's needle jumps to each
headingit is given, so the motion is the screen's own turn of the map, which reads the motion tokens' reduced values (CR-12).
Owned strings
Every word MapControls announces is its own; it shows none. No key in packages/perron/src/locale/en.ts said any of these, so all nine are new, each added when its button joined, each with a JSDoc line like its neighbours, and agents write the Danish, Swedish and Norwegian.
The four new keys are mapControls "Map controls", zoomIn "Zoom in", zoomOut "Zoom out" and centerMap "Centre the map", overridden by mapControlsLabel, zoomInLabel, zoomOutLabel and centerMapLabel. They are the words the Toolbar spec's map stories and the IconButton spec's examples already use, packages/perron/icons/icons.json gives locate the use "Centre the map on the network", each names the action as a verb and what it acts on, in sentence case (Content design), and the keys take the American spelling of code names.
The pause button's two keys joined later: pause "Pause" and resume "Resume", overridden by pauseLabel and resumeLabel. They are the names Mylder's map gives the button in English, and in Danish, "Pause" and "Fortsæt", from its dictionary; the Swedish, "Pausa" and "Fortsätt", and the Norwegian, "Pause" and "Fortsett", were written for Perron, since Mylder has no Swedish or Norwegian. Each is the verb of the press, and packages/perron/icons/icons.json gives pause and play the uses "Stop the vehicles moving on the map" and "Set the vehicles moving again". "Resume" rather than "Play": the map isn't media, and the press resumes the motion the reader paused.
The 3D toggle's and the compass's two keys joined with them, for perron#186, whose changeset names them: view3d and resetNorth, overridden by view3dLabel and resetNorthLabel. Agents write the Danish, Swedish and Norwegian, since Mylder's dictionary has neither yet.
The compass's English, "Reset to north", is the issue's: the verb of the press and where it turns the map, in sentence case, as packages/perron/icons/icons.json gives compass the use "turn the map back to it". The key is resetNorth, the action as the other buttons' keys are.
The compass's description is the key turnedFromNorth, "Map turned away from north", overridden by turnedFromNorthLabel, which joined when the review held the compass to the contract's "everything the eye is told, the ear is told": a statement of the map's state, as the needle shows it, rather than an action, since the name already says the press. Agents write the Danish, Swedish and Norwegian: "Kortet er drejet væk fra nord", "Kartan är roterad bort från norr" and "Kartet er dreid bort fra nord".
The 3D toggle's name is the key view3d, "3D view", overridden by view3dLabel. A toggle's name stays the same while aria-pressed says its state (see Keyboard and ARIA), so it names what the press turns on rather than the press itself: "3D view, pressed" says the map is in 3D, where a verb, "Tilt the map, pressed", reads as a step already taken. "3D" is what the issue and the cube icon's use call the view, and is written alike in all four locales; the key puts the noun first, as mapControls does, since a key can't start with a digit.
| Key | English | Override | Seen or heard where |
|---|---|---|---|
mapControls | "Map controls" | mapControlsLabel | root, heard as the toolbar's name through aria-label |
zoomIn | "Zoom in" | zoomInLabel | zoomIn, heard as the button's name through aria-label |
zoomOut | "Zoom out" | zoomOutLabel | zoomOut, heard as the button's name through aria-label |
centerMap | "Centre the map" | centerMapLabel | centerMap, heard as the button's name through aria-label |
view3d | "3D view" | view3dLabel | tilt, heard as the toggle's name through aria-label, then "pressed" or "not pressed" |
resetNorth | "Reset to north" | resetNorthLabel | compass, heard as the button's name through aria-label, at every heading |
turnedFromNorth | "Map turned away from north" | turnedFromNorthLabel | compass, heard as its description through aria-describedby while the map is turned |
pause | "Pause" | pauseLabel | pause, heard as the button's name through aria-label while paused is false |
resume | "Resume" | resumeLabel | pause, heard as the button's name through aria-label while paused is true |
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | should | Use MapControls for the map's zoom and centre buttons, once per map | Build the same bar from IconButtons or from your own Toolbar: an IconButton in a toolbar adds a tab stop, and a second copy drifts from this one | review |
| DD-2 | should | Place it from the screen, at the bottom right, size.panel-gap from the edges, at z-index.map-controls | Expect MapControls to position itself, or give it a z-index through the component: it sizes to its content and the screen places it | review |
| DD-3 | must | Set zoomInDisabled or zoomOutDisabled when the map reaches a zoom limit | Hide or unmount a button at a limit: focus on it is lost and the bar changes shape under the pointer | review |
| DD-4 | must | Centre the map on the whole network in onCenterMap, as its words promise | Wire it to the reader's own location: its words, "Centre the map", and the locate icon's use promise the network | review |
| DD-5 | must | Rename the bar or a button with its …Label prop, such as zoomInLabel | Pass aria-label to the root: the bar's own name is set after it and replaces it | review |
| DD-6 | must | Bind + and − on the map canvas, where Accessibility puts them | Expect MapControls to listen for keys: it adds none beyond the toolbar's arrows | review |
| DD-7 | should | Give onPausedChange and paused to add the pause button, and set paused from the handler | Put the pause in a Toolbar of its own beside MapControls: it is the map's own control, and a bar of its own adds a second bar and a second tab stop | review |
| DD-8 | must | Put another map action, such as a layers switch, in a Toolbar of its own beside it | Wrap MapControls to add buttons inside it: it takes no children, so its buttons stay the same on every screen; only the map's own controls, the 3D toggle, the compass and the pause, join the bar | types |
| DD-9 | must | Give onTiltedChange and tilted to add the 3D toggle, and set tilted from the map's own tilt, whether the rider tilted it with the toggle or a gesture | Set tilted only in the handler: a gesture that tilts the map then leaves the toggle off, and the ear hears "not pressed" over a tilted map | review |
| DD-10 | must | Give onResetNorth and heading to add the compass, pass the map's bearing in degrees clockwise from north at each frame of a turn, and turn the map back to north in the handler, with the motion tokens' reduced values when the system asks for less motion | Pass the needle's own angle, a counter-clockwise or radian value, or only the heading the turn ends at: the needle turns by minus heading and jumps to each value it is given | review |
| DD-11 | should | Keep the compass in the bar at north | Unmount the compass or drop onResetNorth while the map faces north: the pause button moves under the pointer and the rider can't find the compass when the map is first turned | review |