Skip to main content

ActionHeader

ActionHeader

Responsive app page header with back link, badges and actions that fold into an overflow menu instead of wrapping

Tags: component layout header actions responsive toolbar

View Demo

ActionHeader is the page and record header for dashboard-style apps. Its main feature is that the action row never wraps. As the header gets narrower, secondary actions move into a "More actions" (⋯) menu, so the title and the primary action stay on one line.

Layout responds to the header's own width through container queries, not the viewport width. It therefore adapts when a sidebar opens or collapses, and consumers don't need a Tailwind container-query plugin.

Use PageHeader for marketing and landing-page titles. Use ActionHeader for app pages.

Import​

<script>
  import { ActionHeader, type ActionHeaderAction } from '@classic-homes/theme-svelte';
</script>

Basic Usage​

<ActionHeader
  title="Signs"
  subtitle="Physical signage inventory across all communities."
  actions={[
    { label: 'Export CSV', icon: 'download', onSelect: exportCsv },
    { label: 'Add sign', icon: 'plus', priority: 'primary', onSelect: openCreate },
  ]}
/>

Layout​

wide (≥ 40rem)
← Back to signs
[media] Title ························ [secondary…][Primary][⋯] [tools]
        Subtitle
        [badges]
[toolbar]

narrow (< 40rem)
← Back to signs
Title (2-line clamp) ··········· [Primary][⋯]
Subtitle (2-line clamp)
[badges] ························ [tools]
[toolbar]

At 20rem (320px) or narrower, a primary action that has an icon hides its label visually. The label stays as the accessible name.

Props​

Prop Type Default Description
title string required Page or record title (clamped to two lines)
subtitle string - Description under the title
headingLevel 1 | 2 | 3 1 Heading element for the title
caption string - Small note under the actions, right-aligned (e.g. "Updated 5 minutes ago")
backHref string - Shows a labelled BackButton above the title, linking here
backLabel string - Back target name, shown as "Back to {backLabel}"
actions ActionHeaderAction[] [] Header actions (see below)
collapseAt 'auto' | 'sm' | 'md' | 'lg' | 'never' 'auto' Header width at which secondary actions fold: sm ≈ 28rem, md ≈ 40rem, lg ≈ 52rem. auto means lg with 2+ secondary actions, otherwise md
moreLabel string 'More actions' Accessible name of the ⋯ trigger
media Snippet - Leading visual beside the title, e.g. a thumbnail
titleSuffix Snippet - Small inline content right after the title
badges Snippet - Status badges or a meta line under the title
tools Snippet - Icon buttons that are always visible at the end of the header
toolbar Snippet - Row of controls under the header (view toggles, selects)
class string - Additional classes

ActionHeaderAction​

Field Type Description
label string Button or menu item text
icon IconName Leading icon
href string Render the action as a link
onSelect () => void Click or select handler
priority 'primary' | 'secondary' | 'overflow' Default secondary. See the rules below
variant Button variant Inline button variant. Default: default for primary, outline otherwise
disabled boolean
title string Tooltip on the inline button, e.g. why the action is disabled
loading boolean Shows a spinner inline; disables the item in the menu
destructive boolean Destructive menu styling; sorted last, after a separator
hidden boolean Leaves the action out, so arrays can include permission conditionals inline

Priority rules:

  • primary: always a visible button. Use at most one.
  • secondary: an inline outline button on wide headers. It moves into the ⋯ menu below collapseAt.
  • overflow: always in the ⋯ menu. Use it for rare or destructive actions.

Examples​

Record detail​

<ActionHeader
  title={sign.name}
  subtitle={sign.community}
  backHref="/marketing/signs"
  backLabel="Signs"
  actions={[
    { label: 'Check in', icon: 'check', priority: 'primary', onSelect: checkIn },
    { label: 'Edit', icon: 'edit', onSelect: edit },
    { label: 'Retire', priority: 'overflow', destructive: true, onSelect: retire },
  ]}
>
  {#snippet badges()}
    <Badge>Installed</Badge>
  {/snippet}
  {#snippet tools()}
    <RecordTools />
  {/snippet}
</ActionHeader>

With a toolbar​

<script>
  let groupBy = $state('community');
  let view = $state('list');
</script>

<ActionHeader
  title="All Available Homes"
  subtitle="89 quick move-in homes across all communities"
  caption="Updated 5 minutes ago"
  actions={[{ label: 'PDF', icon: 'file-text', priority: 'primary', variant: 'secondary' }]}
>
  {#snippet toolbar()}
    <Toolbar label="Inventory tools" class="w-full">
      <Input placeholder="Search homes…" class="max-w-sm" />
      <Button variant="secondary" size="sm">Filters</Button>
      <span class="text-sm">89 homes</span>
      {#snippet end()}
        <SegmentedControl
          label="Group homes by"
          size="sm"
          bind:value={groupBy}
          options={[
            { value: 'community', label: 'Community' },
            { value: 'collection', label: 'Collection' },
            { value: 'floor-plan', label: 'Floor Plan' },
          ]}
        />
        <ViewToggle bind:value={view} />
      {/snippet}
    </Toolbar>
  {/snippet}
</ActionHeader>

Accessibility​

  • The title is a real heading (h1 by default). Use headingLevel for nested regions.
  • The title takes the theme's type style for its level: h1 is Spectral 500 italic (2.5rem wide, 1.75rem narrow); h2 and h3 are Figtree 600 (1.75rem / 1.5rem wide). Section headers such as a community name should use headingLevel={2}.
  • The back button's aria-label is "Back to {backLabel}".
  • The ⋯ trigger is a single <button> with an accessible name (moreLabel).
  • A primary action that shows only its icon still has its label as visually hidden text.

Notes​

  • Svelte only. There is no React equivalent yet.
On this page