Skip to main content
WireKit
Copy for LLM

Shell Bar

<x-wirekit::shell-bar> is the head of one column in an application shell: a fixed-height band with a bottom rule, holding a title, a navigation cluster or a set of actions.

Its point is what happens when you use it more than once. A console layout has two or three columns side by side — a module rail, the module's own navigation, the content — and each one needs a head. Draw those three heads by hand and their rules will disagree the first time one column gets a longer title or a taller control, on one breakpoint, without anything turning red. Place a shell bar at the head of each column instead and the rules cannot disagree, because they are the same component reading the same height token.

Usage

A title and its actions

Transactions

The default slot holds the leading cluster; the end slot holds the trailing one and is pushed to the far edge. The leading cluster is allowed to shrink and the trailing one is not — a bar whose buttons squash into unreadable slivers while a heading nobody needs in full stays intact is the wrong trade.

The rule is what aligns

Three columns, three bars, one line. Every bar reads --size-wk-shell-bar for its height, so changing that token moves all of them together.

One rule across three columns

Terminal A920 Pro

The content column fills whatever width is left.

Note the three different padding values. The rail's head is none because its content is a centered mark rather than text on a spine; the second column uses sm, its own narrower tier; the content column uses the default lg, which is the page-edge spine — so a title in the bar sits on the same vertical line as the first paragraph of the content below it.

Stacking two bars

A bar can sit under another one. Turn off the rule on the first so the pair reads as one head rather than as two separated bands:

A title bar over a tab strip

Alignment

align decides how the bar distributes its two clusters. between is the default and is what a title-plus-actions bar wants. start and center are for a bar whose content is one cluster — a centered workspace mark at the head of a rail, for instance.

Centered, with no rule
Centered content

Sticky

sticky pins the bar to the top of its scrolling ancestor.

Read this before reaching for it. position: sticky sticks to the nearest ancestor that actually scrolls. In an app-shell console layout the bar sits inside the content column, and that column never scrolls — the region below it does. So sticky there sticks to something that does not move, which is to say it does nothing. That is not a defect to be fixed; it is what position: sticky means, and the console layout does not need it anyway, because the bar already stays put while the content scrolls beneath it.

Where it does work: a bar placed in ordinary document flow, on a page that scrolls as a document.

Inside a padded column

A sidebar pads itself uniformly. That is right for navigation items and wrong for a chrome band: a bar placed in its header slot ends up inset on three sides, and its rule sits six pixels below the rule in the columns either side of it. That is not an estimate — it is the padding token's value, measured in the browser.

bleed cancels it:

A head that spans its column

It does not hardcode the sidebar's padding. A container that pads its children publishes --wk-nav-pad, and the zone holding the bar reads it. It defaults to zero, so bleed outside such a host does nothing rather than producing a bar that hangs off its container — and any container of your own can opt into being bleedable by publishing that one variable.

The container stands aside; the bar does not escape. That distinction is the whole implementation. The bar used to bleed with negative margins and a width of 100% + 2 × the padding — which worked, and was the fragile shape: an element that relies on overflowing its parent is at the mercy of every ancestor, and any one of them with overflow: hidden, contain: layout, a transform or a filter clips it without warning. The mobile sweep found it as a 6px overflow on a zone that happened not to clip.

Now the zone drops its own inset and pulls back the column's, so the head reaches the column's edge from the inside and never leaves the element it belongs to.

Measured before any of it was fixed: a 256px column, a rule 223px long, and a 33px gap before the column's edge — because bleed emitted both w-full and w-auto, which are equal specificity, and Tailwind sorts w-auto first. The bar moved and never widened.

The bottom is deliberately left alone: the gap under the head is what separates the rule from the first navigation item.

On a narrow screen

The bar keeps an exact height, so its content cannot wrap. The leading cluster therefore scrolls horizontally when it outgrows the column, rather than being cut off.

That is not a nicety. A console top bar with a few navigation items and two actions is easily 520 px wide, and a phone gives it 375 — measured on the console layout, 146 px of it were clipped by the content column, and clipped content is not merely unseen but unreachable, because nothing scrolls a box with no scroller. A scrolling strip of tabs is what phone-sized app bars do, and it costs a desktop nothing: the overflow never engages there.

Where that cluster holds interactive content — tabs, links, buttons — nothing more is owed: Tab reaches each item and the browser scrolls it into view. Where it holds something that is not interactive, such as a long title or a status line, pass label. That turns the cluster into a named, focusable region so a keyboard user can scroll it at all.

Theming

Token Role
--size-wk-shell-bar The bar's height, and therefore the y-coordinate of every column's rule
--color-wk-rail-border The rule's color, falling back to --color-wk-border. Reading the rail role means the segment crossing a toned column matches that column's own edge instead of drawing a light hairline across a dark surface
--padding-wk-x-* Inline padding, selected by the padding prop
--gap-wk-md Gap between the leading and trailing clusters

The bar paints no background of its own. In a toned shell the rail, the second column and the content sit on three different surfaces, and a background here would flatten all three into one — undoing the tone the shell was given.

Props

<x-wirekit::shell-bar>

Prop Type Default Description
rule bool true The bottom border. This is the shell's horizontal rule. Set false for a second, stacked bar that should not draw a line of its own
padding 'none' | 'sm' | 'md' | 'lg' | 'xl' 'lg' Inline padding on the page-edge spine scale. lg is the spine; none is for a rail head whose content is centered; sm matches a sidebar's narrower tier
align 'start' | 'center' | 'between' 'between' Main-axis distribution. between pushes the end slot to the far edge
label string|null null An accessible name for the leading cluster. Needed only when that cluster holds non-interactive content — see below
bleed bool false Cancels the padding of a host that pads its children, so the bar and its rule span the full column. Reads the amount from the host rather than hardcoding it, and is a no-op outside one — see below
sticky bool false Pins the bar to the top of its scrolling ancestor. A no-op inside an app-shell content column — see above
scope string|null null Scoped personalization key

Slots

Slot Purpose
start A pinned leading control — a hamburger, a back arrow, a workspace mark. Keeps its size
default The scrolling middle cluster — a title, a breadcrumb, a tab strip. Gives way first
end The trailing cluster — actions, a search field, an avatar. Keeps its size

Three clusters, and only the middle one moves. The default slot scrolls horizontally once its content is wider than the bar, which on a phone is almost immediately — so a control placed there scrolls out of sight with the tabs. start is for anything that must stay reachable.

The case it exists for is the shell's own hamburger. Below lg, app-shell moves a sidebar or rail into an off-canvas panel, and the only thing that brings it back is a <x-wirekit::sidebar.toggle>. Put that toggle in start:

<x-wirekit::shell-bar label="Terminal sections">
    <x-slot:start>
        {{-- 1. Pinned, so it stays reachable when the sections beside it scroll. --}}
        <x-wirekit::sidebar.toggle class="lg:hidden" aria-label="Open navigation" />
    </x-slot:start>

    {{-- 2. The scrolling cluster. Wider than the bar on a phone, and that is fine. --}}
    <x-wirekit::navbar.item href="#" :active="true">Transactions</x-wirekit::navbar.item>
    <x-wirekit::navbar.item href="#">Terminal info</x-wirekit::navbar.item>

    {{-- 3. Actions keep their size; the middle gives way. --}}
    <x-slot:end>
        <x-wirekit::button size="sm">Create payment</x-wirekit::button>
    </x-slot:end>
</x-wirekit::shell-bar>

Keyboard Interaction

The bar is a layout band and adds no keyboard model of its own — it takes no focus and binds no keys unless you pass label (see above). Whatever you place in it keeps its own: a <x-wirekit::button> is still reached with Tab and fired with Enter or Space, a navigation link with Tab and Enter.

See Also

  • App Rail — the module rail whose head this bar draws
  • App Shell — the layout that puts the columns side by side
  • Sidebar — the second navigation column