Rail Shell
The console shape without a second navigation column: a module rail on the outside, and the content directly beside it as an inset panel. Use it when each area is one screen — a board, a queue, an editor — and a second list of destinations would be a list with one entry.
It is also where the shell's two color decisions become visible. The chrome carries a tone, the content panel does not, and the radius between them is what makes the panel read as a sheet lying on the chrome rather than as a fourth border.
Composed from: app-shell, app-rail, shell-bar, main.
A shell has no outer edge of its own. It fills the browser window, so in a real application its outer edges ARE the window's edges — square, and as wide as the screen. Every radius in this family faces INWARD, toward the inset content panel; nothing faces out. The previews below carry no frame for the same reason: the box they sit in is standing in for the window, and a shell drawn inside a second border is a window inside a window.
Blade Code
{{-- 1. `tone="inverse"` puts the shell's chrome on the dark surface; `panel` lifts the
content column off it as an inset, rounded sheet. --}}
{{-- 2. The rail inherits the shell's tone — it needs no `tone` of its own, and giving it
one is how you make it differ from the chrome deliberately. --}}
<x-wirekit::app-shell viewport panel tone="inverse" header-placement="content">
<x-slot:rail>
{{-- 3. `indicator="edge"` marks the current module with a bar on the rail's
inline-end edge instead of filling its box — which reads better in a very
narrow column, where a filled box dominates everything beside it. --}}
<x-wirekit::app-rail indicator="edge">
<x-slot:brand>
<x-wirekit::shell-bar padding="none" align="center">
<x-wirekit::avatar :initials="auth()->user()->initials" size="sm" />
</x-wirekit::shell-bar>
</x-slot:brand>
<x-wirekit::app-rail.item href="{{ route('invoicing') }}" icon="file-text" label="Invoicing" />
<x-wirekit::app-rail.item href="{{ route('inbox') }}" icon="inbox" label="Inbox" />
<x-slot:footer>
<x-wirekit::app-rail.item href="{{ route('profile') }}" icon="user" label="Account" />
</x-slot:footer>
</x-wirekit::app-rail>
</x-slot:rail>
{{-- 4. With no `sidebar` slot the content column begins directly after the rail. --}}
<x-slot:header>
<x-wirekit::shell-bar>
{{-- 5. The handle for the drawer the rail becomes below `lg`. Without it the
rail is off-canvas on a phone with nothing to open it. --}}
<x-slot:start>
<x-wirekit::sidebar.toggle class="lg:hidden" aria-label="Open navigation" />
</x-slot:start>
<x-wirekit::heading level="1" size="md">AI Invoicing</x-wirekit::heading>
<x-slot:end>
<x-wirekit::button size="sm">Upload invoice</x-wirekit::button>
</x-slot:end>
</x-wirekit::shell-bar>
</x-slot:header>
<x-wirekit::main max="none">
{{ $slot }}
</x-wirekit::main>
</x-wirekit::app-shell>
Letting the rail show its names
An application with one navigation level often wants the rail wide enough to read. Add
expandable and the reader chooses; persist remembers the choice.
Choosing the two colors
The chrome and the content are separate decisions, and both are token-level.
| Want | Set |
|---|---|
| Dark chrome, light content | tone="inverse" on the shell, plus panel |
| A quiet step down from the page | tone="muted" |
| Brand-colored chrome | tone="accent" |
| A rail that differs from the rest of the chrome | a tone on the rail as well as the shell |
| A different radius for the sheet | --radius-wk-shell-panel |
| Different colors entirely | the --color-wk-rail-* roles |
The panel's radius is its own token rather than the generic large radius, because the sheet is the biggest rounded surface on the page: a theme that wants sharp cards with a soft shell — or the reverse — has no way to say so if the two share one value.
The inset is even on all four sides, and that is what makes it read as a sheet. It used to
round only the corners facing the chrome and sit flush against it — which opens a wedge of
chrome beside the rail's straight edge and reads, correctly, as a gap rather than as a panel. A
curve needs something to curve away from. It uses the same token the rail's own panel variant
insets by, so a floating rail and a floating content panel sit on one grid.
A navigation column standing beside an inset panel draws no edge of its own: the gap is the separation, and its border would be a line floating in the middle of the chrome.
Below the breakpoint the inset is dropped, and the edges come back with it. The navigation is a drawer there and the content is the whole viewport, so an inset would spend horizontal room a phone does not have on a decoration nothing is beside.
Would a real browser round the outer corners?
No, and it does not. Every rounded corner in this family faces INWARD — the content panel's corners face the chrome it lies on. The shell's own outer edge carries no radius at all, so in a real full-viewport application it meets the browser's sharp edge squarely, which is the only thing that looks right there.
The rounded outline around these examples is the documentation's preview frame, not the component.
Related
- Console Shell — the same rail with a second navigation column
- App Rail — labeling modes, tones and the expand toggle
- Theming — the token roles behind the tones