Skip to the content

Variants and states

Rest
Line
Stop
  • Rest

Props

NameTypeDefaultDescription
variant'line' | 'stop''line'What the panel shows, as DetailHeader's variant: line for a vehicle or a line, headed by its line badge and destination; stop for a stop or station, headed by its marker and name. Set on the root as data-variant.
line'm1' | 'm2' | 'm3' | 'm4' | 'a' | 'b' | 'c' | 'e' | 'f' | 'h' | 'bus' | 'regional'—The line whose colours the header's mark takes, as DetailHeader's line: in line, the badge's colours, needed with lineName; in stop, the marker's colour, left out for a stop several lines serve.
lineNameReactNode—The line's short name as riders know it, "5C", "M2" or "A", as DetailHeader's lineName: the badge's label in line, also read first in the title and so in the panel's name. Not used in stop.
title*ReactNode—The panel's title, as DetailHeader's title: the destination in line ("Husum Torv"), the stop's name in stop ("Nørreport St."). Required. It names the panel's dialog, after the line's name in line. Replaces the HTML title attribute.
subheaderReactNode—One supporting line under the title, as DetailHeader's subheader, such as "Next stop: Nørreport St." or the lines a stop has.
actionReactNode—The panel's own controls, in a row of their own under the title and the subheader, as DetailHeader's action, such as a "Show on map" button. The close button is the panel's and needs no place here.
childrenReactNode—The panel's details, below the header row, set space.300 apart in a column: for a vehicle its StopTimeline and FactList, for a stop its Sections of departures. It scrolls when it is taller than the panel. The body comes as free children, not as data such as a vehicle or a stop that the panel lays out itself: a vehicle's panel and a stop's hold different patterns, StopTimeline, FactList, DisruptionCard and Sections, which the screen composes, and the panel is their frame, as Sheet is.
anchor'right' | 'bottom''right'The edge the panel slides from, as the Sheet's anchor: right beside the map on a wide screen, bottom on a phone, under breakpoint.phone, which the screen picks. The panel takes only right and bottom, with right the default, and not the Sheet's left: the foundations put the detail panel on the right and the overview sheet on the left, so a left detail panel would sit over the overview sheet.
openboolean—Whether the panel is open, for a controlled panel, as the Sheet's open: the screen opens it when the rider picks a vehicle or a stop.
defaultOpenbooleanfalseWhether the panel starts open, for an uncontrolled panel, as the Sheet's defaultOpen.
onOpen(event, details) => void—Called when the panel asks to open, from its trigger, as the Sheet's onOpen.
onClose(event, reason, details) => void—Called when the panel asks to close, as the Sheet's onClose, with Base UI's reason: escape-key, close-press, swipe, trigger-press and the rest the Sheet lists. details.cancel() keeps it open. A controlled panel sets open to false here, and the screen clears its selection.
triggerReactElement<unknown, JSXElementConstructor<any> | string>—The button that opens the panel, as the Sheet's trigger; it gets the trigger's ARIA and focus back on close. Without one, the panel opens through open, as it does from the map.
snapPointsDrawerSnapPoint[] | undefinedWith anchor 'bottom': size.sheet-peek and 1The heights a bottom panel snaps to, as the Sheet's snapPoints. Ignored with anchor right.
snapPointnull | number | string | undefined—The snap point a bottom panel is at, for a controlled snap point, as the Sheet's snapPoint.
defaultSnapPointnull | number | string | undefinedThe first snap pointThe snap point a bottom panel opens at, for an uncontrolled snap point, as the Sheet's defaultSnapPoint.
onSnapPointChange(event, snapPoint, details) => void—Called when a bottom panel asks to move to another snap point, by a swipe, as the Sheet's onSnapPointChange.
closeLabelstringThe locale'sOverrides "Close", the close button's label; given to the Sheet
classNamestring—Joined after pn-Sheet and pn-DetailPanel on the root, the panel's popup
styleCSSProperties—Reaches the root, the panel's popup
refRef<HTMLDivElement>—Reaches the root element, the panel's popup
slotProps{ header?: Omit<DetailHeaderProps, "action" | "line" | "lineName" | "subheader" | "title" | "variant"> | undefined; trigger?: Props<unknown> | undefined; portal?: DrawerPortalProps | undefined; viewport?: DrawerViewportProps | undefined; close?: DrawerCloseProps | undefined; content?: DrawerContentProps | undefined; }—Props for each part: header, trigger, portal, viewport, close, content.
variant'line' | 'stop', default 'line'
What the panel shows, as DetailHeader's variant: line for a vehicle or a line, headed by its line badge and destination; stop for a stop or station, headed by its marker and name. Set on the root as data-variant.
line'm1' | 'm2' | 'm3' | 'm4' | 'a' | 'b' | 'c' | 'e' | 'f' | 'h' | 'bus' | 'regional', default —
The line whose colours the header's mark takes, as DetailHeader's line: in line, the badge's colours, needed with lineName; in stop, the marker's colour, left out for a stop several lines serve.
lineNameReactNode, default —
The line's short name as riders know it, "5C", "M2" or "A", as DetailHeader's lineName: the badge's label in line, also read first in the title and so in the panel's name. Not used in stop.
title (required)ReactNode, default —
The panel's title, as DetailHeader's title: the destination in line ("Husum Torv"), the stop's name in stop ("Nørreport St."). Required. It names the panel's dialog, after the line's name in line. Replaces the HTML title attribute.
subheaderReactNode, default —
One supporting line under the title, as DetailHeader's subheader, such as "Next stop: Nørreport St." or the lines a stop has.
actionReactNode, default —
The panel's own controls, in a row of their own under the title and the subheader, as DetailHeader's action, such as a "Show on map" button. The close button is the panel's and needs no place here.
childrenReactNode, default —
The panel's details, below the header row, set space.300 apart in a column: for a vehicle its StopTimeline and FactList, for a stop its Sections of departures. It scrolls when it is taller than the panel. The body comes as free children, not as data such as a vehicle or a stop that the panel lays out itself: a vehicle's panel and a stop's hold different patterns, StopTimeline, FactList, DisruptionCard and Sections, which the screen composes, and the panel is their frame, as Sheet is.
anchor'right' | 'bottom', default 'right'
The edge the panel slides from, as the Sheet's anchor: right beside the map on a wide screen, bottom on a phone, under breakpoint.phone, which the screen picks. The panel takes only right and bottom, with right the default, and not the Sheet's left: the foundations put the detail panel on the right and the overview sheet on the left, so a left detail panel would sit over the overview sheet.
openboolean, default —
Whether the panel is open, for a controlled panel, as the Sheet's open: the screen opens it when the rider picks a vehicle or a stop.
defaultOpenboolean, default false
Whether the panel starts open, for an uncontrolled panel, as the Sheet's defaultOpen.
onOpen(event, details) => void, default —
Called when the panel asks to open, from its trigger, as the Sheet's onOpen.
onClose(event, reason, details) => void, default —
Called when the panel asks to close, as the Sheet's onClose, with Base UI's reason: escape-key, close-press, swipe, trigger-press and the rest the Sheet lists. details.cancel() keeps it open. A controlled panel sets open to false here, and the screen clears its selection.
triggerReactElement<unknown, JSXElementConstructor<any> | string>, default —
The button that opens the panel, as the Sheet's trigger; it gets the trigger's ARIA and focus back on close. Without one, the panel opens through open, as it does from the map.
snapPointsDrawerSnapPoint[] | undefined, default With anchor 'bottom': size.sheet-peek and 1
The heights a bottom panel snaps to, as the Sheet's snapPoints. Ignored with anchor right.
snapPointnull | number | string | undefined, default —
The snap point a bottom panel is at, for a controlled snap point, as the Sheet's snapPoint.
defaultSnapPointnull | number | string | undefined, default The first snap point
The snap point a bottom panel opens at, for an uncontrolled snap point, as the Sheet's defaultSnapPoint.
onSnapPointChange(event, snapPoint, details) => void, default —
Called when a bottom panel asks to move to another snap point, by a swipe, as the Sheet's onSnapPointChange.
closeLabelstring, default The locale's
Overrides "Close", the close button's label; given to the Sheet
classNamestring, default —
Joined after pn-Sheet and pn-DetailPanel on the root, the panel's popup
styleCSSProperties, default —
Reaches the root, the panel's popup
refRef<HTMLDivElement>, default —
Reaches the root element, the panel's popup
slotProps{ header?: Omit<DetailHeaderProps, "action" | "line" | "lineName" | "subheader" | "title" | "variant"> | undefined; trigger?: Props<unknown> | undefined; portal?: DrawerPortalProps | undefined; viewport?: DrawerViewportProps | undefined; close?: DrawerCloseProps | undefined; content?: DrawerContentProps | undefined; }, default —
Props for each part: header, trigger, portal, viewport, close, content.

Use it

import { DetailPanel } from '@mylder/perron/patterns'

Ask your agent

search_docs({ query: "DetailPanel" })

Keyboard and ARIA

From the Sheet and its Base UI Drawer. "From" says who does it: Base UI, the Sheet, or the pattern.

KeyDoesFrom
Enter, SpaceOn the trigger, opens the panel and moves focus to the popupBase UI
TabFrom the popup, moves to the controls in action, then to the close button, then to the body while it scrolls, in reading orderThe browser, in the DOM order the pattern sets (CR-2)
Tab, Shift+TabMoves on out of the panel into the page; the panel stays open, since it is not modalBase UI, with the Sheet's disablePointerDismissal
Enter, SpaceOn the close button, closes the panel with reason close-press, and focus returns to the trigger or to the element focused beforeBase UI
EscCloses the panel with reason escape-key when it is the topmost open sheet, after any dialog above it, and focus returns as aboveBase UI
Arrows, PageUp, PageDown, Home, EndScroll the body while it has focusThe Sheet, with tabIndex 0 while the body overflows
ElementRoleAccessible name fromStates announced
triggerbuttonThe caller's elementaria-expanded, aria-haspopup="dialog", with aria-controls naming the popup
rootdialogThe DetailHeader's h2, through the panel's aria-labelledby: "5C Husum Torv" in line, "Nørreport St." in stopNone: it is not modal, so the page stays in the accessibility tree
headerNone (generic div, in the Sheet's title div)NoneNone
titleheading, level 2, DetailHeader'sIts text: in line the hidden lineName then title, in stop titleNone
closebuttonThe owned close string, or closeLabelNone
contentNone (generic div)NoneNone

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: the header's badge or marker is aria-hidden and its line is read in the title, which names the panel; the close button's icon is aria-hidden and the CloseButton part gives it the owned label; the body scrolls by keys and screen reader at every snap point, as the Sheet makes it.
  • Every landmark and named region has an accessible name: the dialog is named by DetailHeader's h2 through aria-labelledby (CR-3), never by the whole header row, so the subheader and the action's names stay out of it.
  • Every default label can be overridden: closeLabel, given to the Sheet.
  • Every shape the type accepts renders, in every variant: title, lineName, subheader, action and children show a string, an element and a list alike, in line and stop; the Line and Stop stories' play tests open the panel with each but action, and the WithAction story's with action.
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; a closed panel is unmounted, or hidden when slotProps.portal.keepMounted keeps it, as the Sheet does.
  • Native semantics first: the h2 from DetailHeader, <button> for the close button and the trigger, and Base UI's dialog role on the root; the panel adds only aria-labelledby.
  • Focus moves in reading order and returns on close: the popup, then the header's action, under the title it acts on, then the close button, the last control of the header as the Sheet places it in every sheet, then the body; Base UI returns focus to the trigger or to the element focused before.
  • Every target is at least size.target-min square: the close button, size.control; the action's controls keep their own sizes.
  • Loops stop when the system asks for less motion: none here; the slide reads duration.sheet, which the tokens shorten to 0.01 ms.

Owned strings

Every word the component shows or announces on its own. Reuse a key packages/perron/src/locale/en.ts already has; a new key gets its English here, and agents write the Danish, Swedish and Norwegian in the locale files.

KeyEnglishOverrideSeen or heard where
closeClosecloseLabelThe Sheet's close button at the end of the header row, announced as its name

Every other word comes from the caller, through lineName, title, subheader, action and children; the panel adds no key.

Do and don't

For a caller using the pattern, 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.

RuleLevelDoDon'tChecked by
DD-1mustOpen one detail panel at a time, controlled by open, for the vehicle or stop the rider picked, and clear the selection in onCloseStack a second detail panel over the first; pick the new vehicle into the open panel, so Esc still closes the topmost thing firstreview
DD-2shouldGive the destination as title and the line's short name as lineName: "Husum Torv" with "5C"Put the line in the title as well, "5C Husum Torv": the panel already reads the line first, so its name would say it twicereview
DD-3mustPut at most one control of your own in action, such as "Show on map", and let the panel draw its close buttonPut a close button of your own in action: the panel has one, at the end of the header row, which Esc and focus return go withreview
DD-4shouldPick anchor bottom under breakpoint.phone, in the screen's codeExpect the panel to turn into a bottom sheet on its own; it holds no media queryreview
DD-5mustCompose the body from Perron's patterns and components, StopTimeline, FactList, Section and DisruptionCard, with their own h3 headingsPut a heading of level 2 in the body: the panel's title is its h2, and a section inside it is an h3review
DD-6mustKeep one primary action per panelPut a brand button in action and another in the body; a screen has one brand action at mostreview