Layout
App shell: sticky header that hides on scroll down, footer, left / right drawers that sit beside the page on wide screens and slide over it below the breakpoint, edge-swipe to open on touch, safe-area padding for notches and the home bar.
An app shell: header, page, and side drawers that sit beside the page on wide screens and over it on narrow ones. For an official site's frame with a fixed sidebar, Stage.
Dashboards, admin tools, mobile apps: the same header, footer and drawers frame every screen.
BlessLayout owns the viewport, so it can't sit inside a doc page — open it and resize the window:
Open Layout on a page of its own ↗
vue
<script setup lang="ts">
import { ref } from "vue";
import { withBase } from "vitepress";
import { BlessBottomTabs, BlessButton, BlessLayout, BlessSidebarNav, BlessText } from "blessing-ui";
import { navItems } from "./_nav";
const left = ref(false);
const right = ref(false);
const tab = ref("home");
</script>
<template>
<BlessLayout v-model:left="left" v-model:right="right" :breakpoint="900">
<template #header>
<div style="display: flex; align-items: center; gap: 12px; padding: 10px 16px">
<BlessButton size="sm" variant="ghost" aria-label="Menu" @click="left = !left"
>☰</BlessButton
>
<BlessText weight="bold" style="flex: 1">Blessing</BlessText>
<BlessButton size="sm" variant="ghost" aria-label="Filters" @click="right = !right"
>⚙</BlessButton
>
</div>
</template>
<template #left>
<div style="padding: 16px">
<BlessSidebarNav
:items="navItems"
active="#news"
@select="
(i, e) => {
if (!i.external) {
e.preventDefault();
left = false;
}
}
"
/>
<BlessText as="p" size="xs" muted style="margin-top: 24px"
><a :href="withBase('/components/layout')">← back to docs</a></BlessText
>
</div>
</template>
<template #right>
<div style="padding: 16px">
<BlessText weight="bold">Filters</BlessText
><BlessText as="p" size="sm" muted>right drawer</BlessText>
</div>
</template>
<div style="padding: 16px; max-width: 720px">
<BlessText as="h1" size="lg" weight="light">Layout</BlessText>
<BlessText as="p" size="sm" muted
>Resize below 900px: drawers go off-canvas, swipe in from the edges on touch, header hides
on scroll down. Above: drawers sit beside the page.</BlessText
>
<BlessText v-for="n in 80" :key="n" as="p"
>第{{ n }}話 — scroll to see the header reveal</BlessText
>
</div>
<template #footer>
<BlessBottomTabs
v-model="tab"
inline
:items="[
{ label: 'Home', value: 'home', icon: '⌂' },
{ label: 'Search', value: 'search', icon: '⌕' },
{ label: 'Me', value: 'me', icon: '◯' },
]"
/>
</template>
</BlessLayout>
</template>- On narrow screens a drawer opens over the page (
v-model:left/v-model:right): focus moves into it, and Esc or the backdrop closes it and returns focus. Closed, it is inert. A swipe from the edge opens it on touch (swipe). revealhides the header while scrolling down and brings it back on the way up.breakpointsets where drawers move beside the page.
Usage
ts
import { BlessLayout } from "blessing-ui";API
Props
| Name | Type | Default | Description |
|---|---|---|---|
breakpoint | number | 1024 | viewport width at and above which drawers sit beside the page instead of over it |
reveal | boolean | true | header hides on scroll down, shows on scroll up |
leftWidth | string | "280px" | drawer widths |
rightWidth | string | "280px" | |
swipe | boolean | true | swipe from the screen edge opens a drawer on touch |
edge | number | 24 | the drawer that swiping opens when both exist |
left | boolean | false | |
right | boolean | false |
Events
| Name | Payload | Description |
|---|---|---|
update:left | [value: boolean] | |
update:right | [value: boolean] |
Slots
| Name | Scope | Description |
|---|---|---|
header | any | |
footer | any | |
left | any | |
right | any | |
default | any |