Skip to content

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.

NNorthwind

Row 1

Row 2

Row 3

Row 4

Row 5

Row 6

Row 7

Row 8

Row 9

Row 10

Row 11

Row 12

Version 1.4.0

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.

Header

Row 1

Row 2

Row 3

Row 4

Row 5

Row 6

Row 7

Row 8

Row 9

Row 10

Row 11

Row 12

Version 1.4.0

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:

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

Northwind

Row 1

Row 2

Row 3

Row 4

Row 5

Row 6

Version 1.4.0

sidebarPosition moves the column; ISidebar's own side decides which edge carries its border. Set both.

Without a sidebar the shell is a header, a content column and the footer.

Northwind

Row 1

Row 2

Row 3

Row 4

Row 5

Row 6

Row 7

Row 8

Version 1.4.0

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.

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.

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

vue
<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 ​

PropTypeDefaultDescription
asstring'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
mobileNavbooleantrueMove the sidebar into a drawer below md
navLabelstring'Open navigation'Accessible name for the drawer trigger
unstyledboolean—Drop built-in classes
classstring—Merged with the built-in classes
ui{ root?, header?, headerRow?, headerContent?, navTrigger?, navDrawer?, navDrawerBody?, body?, sidebar?, main?, footer? }—Per-element class overrides

Slots ​

SlotWhen to use it
defaultThe page itself, inside <main>
headerApp bar. Receives navOpen and toggleNav. Measured in page mode to position the sticky sidebar
sidebarThe navigation rail — usually ISidebar. Receives inDrawer — false in the rail, true in the mobile drawer, where the slot is rendered a second time
footerPinned 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.