Skip to content
RU
Scale
Density
Theme
Accent

Data display

Panel

A region of an application: it stands on the screen permanently and has a name of its own. A panel is not a card: it cannot be dragged, because it IS the place.

The header draws no rule under itself. A panel already encloses its contents once, with its own edge; a line across it thirty pixels in is a second enclosure of the same object. The title is a rung under its data and set in medium, which is what makes it read as a header. The one exception is a toolbar directly under the header — a strip of controls, whose own bottom edge is what parts it from the data.

The component
Runs
The content of the region.
Markup
<div class="inst-panel">
  <div class="inst-panel-header">
    <span class="inst-panel-title">Runs</span>
    <div class="inst-panel-actions">
      <button class="inst-btn inst-btn--sm" type="button">Refresh</button>
    </div>
  </div>
  <div class="inst-panel-body">
    The content of the region.
  </div>
</div>
Example
Runs
No edge: the step from the page to the sheet is the boundary. Large regions only.
Markup
<div class="inst-panel inst-panel--plain">
  <div class="inst-panel-header"><span class="inst-panel-title">Runs</span></div>
  <div class="inst-panel-body">No edge: the step from the page to the sheet is the
    boundary. Large regions only.</div>
</div>

Contract

What Required Why
--plain only on a large region yes The boundary then rests on a step of 1.07 — measured to hold across 201 000 px² and to be lost across 36 000. A small panel owes its edge
role="region" and aria-labelledby yes, if the panel is a landmark Otherwise it will not appear in a screen reader's list of regions
tabindex="0" on the body yes, if there is nothing focusable inside A scrolling region has to be able to take focus from the keyboard
The width comes from outside yes A panel is declared a container; container-type switches off the intrinsic size and will collapse an element that takes its width from its content

Accessibility

The heading inst-panel-title is visual. If the panel is a landmark, give it role="region" and an aria-labelledby pointing at the heading, otherwise it will not appear in the list of regions
Scrolling The body scrolls by itself. A scrolling region has to be able to take focus from the keyboard — browsers give that to a tabindex="0" when there is nothing focusable inside
Depth Carried by the surface rather than by a shadow: --surface-raised over --surface-page. In forced-colors the surfaces collapse, so a panel gets an explicit CanvasText border

Anatomy

The body of a panel

<div class="inst-panel-body inst-panel-body--list">…</div>
Class When
inst-panel-body The default: inner padding, scrolling of its own
inst-panel-body--flush The padding is removed — the content knows about the edges itself (a table, a diff)
inst-panel-body--list For a list of rows: padding on the vertical but not on the horizontal

Variants

A panel has no -- modifiers and takes an attribute instead, because what changes is not the panel but its place: the same panel is the point of one screen and a footnote on another. The vocabulary is closed at three, and the full contract is in design principles.

Example
Current run
worldgen-01
A card inside a lead panel is an ordinary card
Recent runs
Four finished in the last hour.
Retention
Transcripts are kept for thirty days.
Markup
<div class="inst-stack">
  <div class="inst-panel" data-rank="lead">
    <div class="inst-panel-header"><span class="inst-panel-title">Current run</span></div>
    <div class="inst-panel-body">
      <div class="inst-card">
        <div class="inst-card-title">worldgen-01</div>
        <div class="inst-card-sub">A card inside a lead panel is an ordinary card</div>
      </div>
    </div>
  </div>
  <div class="inst-panel" data-rank="default">
    <div class="inst-panel-header"><span class="inst-panel-title">Recent runs</span></div>
    <div class="inst-panel-body">Four finished in the last hour.</div>
  </div>
  <div class="inst-panel" data-rank="support">
    <div class="inst-panel-header"><span class="inst-panel-title">Retention</span></div>
    <div class="inst-panel-body">Transcripts are kept for thirty days.</div>
  </div>
</div>

Rank does not descend. The card above carries no rank of its own, so it is an ordinary card even though the panel holding it leads the screen. A rank that inherited would be a dimmer with a nicer name; what a lead region leads is the screen, not its own contents.

Lead moves one thing. Its name takes one rung up the type ladder and nothing else: same ground, same edge. There is nowhere to lift a panel to — the raised surface is the top of the ramp in every light theme — so presence is spent on size, which the kit's ban on heavier weights already makes the only carrier of large-scale hierarchy.

Support may spend more, because withdrawing is the cheap direction: a rung down, an ink tier down, a recess film and no edge at all.

Rank is not tone. A lead panel is the principal one, not the good one. The two combine — a data-rank="lead" data-tone="error" panel is an incident that owns its screen.

Behavior

A panel is a container

A panel is declared a container (container: panel / inline-size), so the components inside answer to its width. One and the same panel stands both in the narrow column of an inspector and across the whole width of a dashboard.

/* A rule of your own for a narrow panel */
@container panel (inline-size < 20rem) {
  .my-thing { grid-template-columns: 1fr; }
}

The width always comes to a panel from outside, so declaring it a container is safe. Declaring something a container when it takes its width from its content is not allowed: container-type switches off the intrinsic size on the inline axis and will collapse the element.

Something popping up inside a panel

A popover inside a panel will be clipped. A panel has overflow: hidden for the sake of its radius. The library's contract: what pops up is drawn in the top layer — [popover] or a portal — rather than inside the flow of the panel. The same goes for the tooltip: inside a panel a popover is wanted in its place.

API

Classes
NameWhat it does
class
inst-panelThe region. A container on the inline axis
inst-panel-headerThe header: a name and actions
inst-panel-titleThe name of the region
inst-panel-actionsThe actions in the header, pushed to the end
inst-panel-bodyThe body, with scrolling
modifier
inst-panel-body--flushWith no inner padding
inst-panel-body--listPadding on the vertical only
inst-panel--plainNo edge: the boundary is carried by the step of surface alone. For large regions
Attributes
NameValueWhat it does
attribute
data-rank—How much attention this region asks for: lead / default / support
Tokens
NameValueWhere from
token
--surface-raisedlight-dark(var(--n-0), var(--n-12))base +1: theme
--borderlight-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11))one across every cell
--hairline1pxbase +1: screen
--radius-lg12pxbase +4: scale
--pad-panelvar(--space-5)base +14: density · scale
--text-sm0.875rembase +4: scale
--region-title-leadvar(--text-sm)one across every cell
--region-title-defaultvar(--text-xs)one across every cell
--region-title-supportvar(--text-2xs)one across every cell
--region-ground-supportvar(--surface-recessed)one across every cell
--region-edge-supporttransparentone across every cell

Why the kit works this way — the constitution · Open source under MIT, github.com/keshon/instrument