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
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.
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:
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.
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:
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.