Layout
IAppShell
The page frame: header, sidebar, main and footer. Every slot takes whatever you put in it — navigation, account menu and routing stay yours, and ISidebar works with or without the shell.
Row 1
Row 2
Row 3
Row 4
Row 5
Row 6
Row 7
Row 8
Row 9
Row 10
Row 11
Row 12
Each of header, sidebar and footer is rendered only when you fill it, so a shell with just a header is a header and a content column, with no empty rails left behind.
Two scroll models
scroll picks between two layouts.
main, the default, pins the shell to the viewport and scrolls only the content column. The header and sidebar never move — what a data app wants.
page scrolls the document, with a sticky header and a sticky sidebar. Anchor links and the browser's scroll restoration work in this mode, so content and marketing pages want it. Being sticky rather than fixed, the sidebar travels up with the end of the body and the footer pushes it off screen.
Row 1
Row 2
Row 3
Row 4
Row 5
Row 6
Row 7
Row 8
Row 9
Row 10
Row 11
Row 12
The shell itself is the scroll container here, and the header sticks within it.
page mode sizes its sidebar against 100svh, so the boxed demo above overrides three values to keep the rail inside its frame:
<IAppShell
scroll="page"
:ui="{
root: 'h-72 min-h-0 overflow-y-auto',
body: 'flex-none',
sidebar: 'h-[calc(18rem-var(--iryx-shell-header-height,0px))]',
}"
>A full-page shell needs none of that.
In page mode the shell measures its own header and publishes the height as --iryx-shell-header-height. Use that variable to offset anything you stick below the header yourself.
Sidebar on the right
Row 1
Row 2
Row 3
Row 4
Row 5
Row 6
sidebarPosition moves the column; ISidebar's own side decides which edge carries its border. Set both.
Where the footer sits
Without a sidebar the shell is a header, a content column and the footer.
Row 1
Row 2
Row 3
Row 4
Row 5
Row 6
Row 7
Row 8
In main mode the footer is pinned below the scrolling column, so it stays visible however long the page is. In page mode it sits at the end of the document, where a footer normally lives.
Navigation on a phone
Below md the sidebar column is hidden and the same #sidebar slot is rendered inside a drawer instead, with a trigger the shell puts in the header itself. Nothing needs wiring up: a shell with a sidebar has usable navigation on a phone by default.
A sidebar narrow enough for a phone is not a sidebar, and one that merely squeezes takes the content column down with it.
<IAppShell>
<template #header><!-- your bar --></template>
<template #sidebar>
<ISidebar :items="items" class="w-60" />
</template>
</IAppShell>The breakpoint is CSS, not a media query read in script, so server-rendered markup and the first client frame agree on what to draw.
The slot is rendered twice — once as the column, once in the drawer — and is told which is which, so a brand or a footer can appear in only one of them:
<IAppShell>
<template #sidebar="{ inDrawer }">
<ISidebar :items="items" class="w-60">
<template v-if="inDrawer" #header>
<MyWordmark />
</template>
</ISidebar>
</template>
</IAppShell>Set mobileNav to false to turn the whole thing off. To place the trigger yourself rather than take the shell's, the #header slot receives navOpen and toggleNav.
The header slot sits in a row
To make room for that trigger, the shell wraps #header in a flex row. A bar that draws its own bottom rule should move it to ui.header — from inside the content column, the rule stops short of the trigger.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as | string | 'div' | Element or component to render as |
scroll | 'main' | 'page' | 'main' | Scroll the content column, or the document |
sidebarPosition | 'left' | 'right' | 'left' | Which side the sidebar rail sits on |
mobileNav | boolean | true | Move the sidebar into a drawer below md |
navLabel | string | 'Open navigation' | Accessible name for the drawer trigger |
unstyled | boolean | — | Drop built-in classes |
class | string | — | Merged with the built-in classes |
ui | { root?, header?, headerRow?, headerContent?, navTrigger?, navDrawer?, navDrawerBody?, body?, sidebar?, main?, footer? } | — | Per-element class overrides |
Slots
| Slot | When to use it |
|---|---|
| default | The page itself, inside <main> |
header | App bar. Receives navOpen and toggleNav. Measured in page mode to position the sticky sidebar |
sidebar | The navigation rail — usually ISidebar. Receives inDrawer — false in the rail, true in the mobile drawer, where the slot is rendered a second time |
footer | Pinned below the content in main mode, at the end of the document in page mode |
Pair it with IContainer for the content width and IPageHeader for the title row.