Usage
App is the root of a standalone Meteor application: it fills the viewport, lays out the header, both sidebars and the content, and turns the sidebars into drawers on small screens. It also provides the theme, future flags and the Snackbar host, while routing, navigation and page content stay yours.
import { MtApp, useMtApp } from "@shopware-ag/meteor-component-library";
Examples
Application setup
Fill the slots you need and render your router view in content. Empty slots leave no region behind.
Regions
Every slot is optional. Absent or empty regions leave neither an element nor a gap behind, and the remaining regions take the space. When only content is filled, it fills the whole shell without a frame.
Theme
The shell applies the stored theme preference. Change it from any component inside the shell with useTheme, for example together with Theme Select.
Fullscreen views
A view hides the header and sidebars with useMtAppRegions, for example a route with a fullscreen editor. The regions return when the view unmounts.
Reading the shell state
useMtApp() gives descendants read access to the layout mode, the open drawer and the theme, plus openDrawer, closeDrawer and setTheme.
Anatomy
header: spans the full width with an 8px inset on both sides and keeps its own height. The sidebars and the content sit directly below it; without a header, they keep an 8px gap to the top edge, unless the content is shown without a frame. In the mobile layout, the shell adds a drawer trigger for each filled sidebar at the start and the end of the header, 8px from its content, so leave out your own inline padding there.isMobilefrom the slot props or from useMtApp tells you which layout is active. Without header content, the shell renders a minimal header that holds only the triggers.sidebar-startandsidebar-end:complementarylandmarks in the desktop layout. Below the mobile breakpoint their content moves into a Drawer without being re-mounted, so component and form state inside them survive every layout change. Each drawer has a close button, a backdrop, Escape and swipe handling and its own translated accessible name. The drawers use the floating look of Drawer.content: the<main>landmark and the scroll container of the page content, available asscrollContainerfromuseMtApp(). The sidebars scroll on their own when their content is taller than the shell.global: app-wide hosts that render no layout box, such as notification renderers or keyboard-shortcut listeners. They stay mounted across route changes.
API reference
Props
| Prop | Type | Default |
|---|---|---|
futureFuture flags for every Meteor component inside the shell. All flags,
including upcoming ones, are enabled by default; the given flags override
that, e.g. `{ removeCardWidth: false }` keeps every other flag enabled and
`{ all: false }` disables all of them. | any | undefined |
mobile-breakpointThe viewport width in pixels below which the shell switches to the mobile
layout and the sidebars become off-canvas drawers. `0` disables the
mobile layout. | number | 1280 |
Slots
| Slot | Bindings |
|---|---|
headerThe header bar. In the mobile layout the drawer triggers are placed at its start and end. | { isMobile: boolean; } |
sidebar-startThe start sidebar, usually the navigation. Becomes a drawer in the mobile layout. | SidebarSlotProps |
contentThe scrollable main content. | any |
sidebar-endThe end sidebar, for example an assistant or contextual tools. Becomes a drawer in the mobile layout. | SidebarSlotProps |
globalApp-wide hosts without a layout box, such as keyboard shortcut listeners. | any |
Exposed
| Name | Type |
|---|---|
openDrawerOpens the drawer of the given side (mobile layout only). | (side: MtAppSide) => void |
closeDrawerCloses the open drawer. | () => void |
Best practices
- Mount App once, as the root of a standalone application, and keep wrappers around it free of margins, padding and other content: the shell is one viewport tall and the document does not scroll.
- Hide regions for a single view with useMtAppRegions instead of removing slot content from the root component.
- Render your router view inside the
contentslot and keep page padding inside your pages. - Rely on the future flags App enables by default, and opt out of single flags through
futureonly when a change does not fit your application yet.
- Do not render more than one App in an application, not even in separate subtrees.
- Do not use the shell inside Administration extensions, in iframes that the host sizes to their content, or in existing page layouts; keep the Theme Provider and your host's layout there.
- Do not mount your own Snackbar host; the shell already renders one, and only one host renders the notifications at a time.
- Do not render another
<main>element inside the content slot. - Do not give your own content inside the shell a z-index above
--z-index-drawer(900); it would cover the drawers.
Behavior
- Mobile breakpoint. Below
mobileBreakpoint(1280px by default) the shell switches to the mobile layout, marked withdata-layout="mobile"on its root. A value of0disables the mobile layout. - Height. The shell is
100dvhtall (with a100vhfallback). Override it with the--mt-app-heightcustom property. - Drawers. At most one drawer is open. Opening the other side closes the first one, and leaving the mobile layout closes any open drawer and removes all modal state.
- Scrolling. The content panel scrolls, and each sidebar scrolls on its own when its content overflows. While the shell is mounted, users cannot scroll the document itself, and the page behind the shell shows the shell background. This lock is plain CSS and already works before hydration. An open drawer needs no scroll lock either: its backdrop covers the content, and the content keeps its scroll position.
- Routing. When the application uses Vue Router, the shell picks it up on its own: every completed navigation closes an open drawer, also a link to the page that is already shown, a navigation to another path scrolls the content to the top, back and forward restore the scroll position of the content for that history entry, and a URL hash scrolls its target into view. The router's
scrollBehaviorhas no effect, because it scrolls the window, while the shell scrolls the content panel. With another router, callcloseDrawer()and scrollscrollContainerfrom useMtApp yourself. - Hidden regions. Views hide the header and sidebars with useMtAppRegions. Hidden regions stay mounted, lose their drawer trigger, and come back when the last view that hides them unmounts.
- Content frame. The content sits in a bordered panel with an 8px inset. When the
headerand both sidebar slots are empty, the frame and the inset go away and the content fills the shell. Regions that a view hides keep the frame, so the view looks like the other views of the application; withcontentFrame: false, useMtAppRegions removes it while no other region is visible. - Printing. The printout leaves out the header, the sidebars and the backdrop, and prints the content in its full length instead of the visible part of the scroll panel.
- Future flags. All future flags are enabled by default, including the ones added in later releases, so the shell always previews the next major.
futureapplies on top of that:{ removeCardWidth: false }opts out of a single flag and keeps all others,{ all: false }opts out of all of them, and{ all: false, removeCardWidth: true }enables a single one. - Theme. The shell reads the preference from
localStorage(mt-theme,systemby default), writes the resolved theme to<html data-theme>and follows changes made through useTheme orsetThemefrom useMtApp. - One shell. Each shell is one viewport tall and the document does not scroll while a shell is mounted, so a page has room for a single App.
Server-side rendering
The shell renders on the server and hydrates without mismatches. The server knows neither the viewport nor the stored theme, which leads to these differences until the page hydrates:
- Below 1280px the sidebars stay hidden, then the mobile layout with its drawer triggers appears. A custom
mobileBreakpointonly applies after hydration. - Regions that a route hides with useMtAppRegions still show.
- The persisted theme applies after hydration. To avoid a flash of the wrong theme, apply it with an inline script in
<head>that runs before the page paints:
<script>
(function () {
var theme = localStorage.getItem("mt-theme");
if (theme !== "light" && theme !== "dark") {
theme = matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
document.documentElement.dataset.theme = theme;
})();
</script>
Layering
Overlays keep their render targets and stack in this order. Most layers read their z-index from a custom property that the global stylesheet sets on :root, so an application can move a whole layer; without the global stylesheet the same values apply as fallbacks. Select result lists and the date picker use fixed values.
| Layer | Custom property | Default | Rendered in |
|---|---|---|---|
| Header, sidebars, content | document order | shell | |
| Drawer backdrop and drawers | --z-index-drawer | 900 | body |
| Modal | --z-index-modal | 1000 | body |
| Popover, context buttons | --z-index-popover | 1070 | body |
| Tooltip | --z-index-tooltip | 1100 | body |
| Select result lists | 1100 | body | |
| Action Menu | --z-index-menu | 1300 | body |
| Snackbar | --z-index-notification | 1600 | body |
| Date picker | 99999 | body |
Drawers and modals share one modal layer. While one is open everything behind it is inert, and overlays opened from inside it, such as menus, popovers, select result lists, date pickers and snackbars, stay usable. A modal opened from a drawer stacks above it. Escape closes the innermost layer only: an open select result list or tooltip first, then the modal, then the drawer.
Accessibility
- The shell renders a
headerand amainlandmark for its filled regions. In the desktop layout, each filled sidebar is acomplementarylandmark with a translated name ("Primary sidebar" or "Secondary sidebar"); in the mobile layout these names label the drawers. - A translated "Skip to content" button is the first focusable element. It stays visually hidden until it receives focus and moves the focus to the content, so keyboard users can scroll the content right away.
- Drawer triggers carry
aria-expandedandaria-controls. An open drawer is a modal dialog: the focus moves into it, Tab and Shift+Tab stay inside, the rest of the page becomes inert, and Escape, the close button or the backdrop close it and return the focus to where it was before the drawer opened, usually the trigger. - Closed drawers are unreachable for keyboard and assistive technology. When a region is hidden while it holds the focus, the focus moves to the content.
- The slide animation is skipped when the user prefers reduced motion.
Related components
- Drawer: when a panel should slide in on demand, independent of the shell's sidebars.
- Theme Provider: when a Meteor view is embedded, for example in an Administration extension, and only needs future flags.
- Container: when page content inside the shell should keep a readable maximum width.
- Theme Select: when users should pick the theme the shell applies.