Skip to content

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).
  • reveal hides the header while scrolling down and brings it back on the way up. breakpoint sets where drawers move beside the page.

Usage ​

ts
import { BlessLayout } from "blessing-ui";

API ​

Props

NameTypeDefaultDescription
breakpointnumber1024viewport width at and above which drawers sit beside the page instead of over it
revealbooleantrueheader hides on scroll down, shows on scroll up
leftWidthstring"280px"drawer widths
rightWidthstring"280px"
swipebooleantrueswipe from the screen edge opens a drawer on touch
edgenumber24the drawer that swiping opens when both exist
leftbooleanfalse
rightbooleanfalse

Events

NamePayloadDescription
update:left[value: boolean]
update:right[value: boolean]

Slots

NameScopeDescription
headerany
footerany
leftany
rightany
defaultany

Released under the MIT License. Character names and artwork belong to their owners.