Layouts
A layout is the shell around your features: the sidebar, the navigation, the header, the settings frame. Every project has exactly one, and the five web shells are interchangeable. Swap one for another and your feature code does not change.
| Pack | Price | The look |
|---|---|---|
layout-basic | Free | A classic dashboard: a sidebar that collapses to an icon rail, a brand logo in the header, an inset content panel with breadcrumbs |
layout-crm | Paid | A compact workspace with persistent grouped navigation, nested route tabs, and a bundled appearance preset with pill controls |
layout-glass | Paid | Glassmorphism: a frosted, blurred sidebar floating over soft color flairs, with an opaque content card |
layout-joyful | Paid | Playful: a floating sidebar panel painted in the brand color, pill navigation, round icons when collapsed |
layout-native-app-shell | Paid | One shell for web and Electron: primary, secondary, and tertiary navigation, a mobile drawer, and macOS-style window chrome on desktop |
Install one with compose, or pin it for CI:
npx @hype-stack/cli compose --packs starter-saas-betterauth,layout-glassNo special integration needed
Layouts are frontend, mobile, and extension code only. No backend files, no Prisma models, no environment variables, no third-party account. All layouts ship frontend, admin, mobile, and extension shells.
What it depends on
- A starter pack for auth. A layout renders the signed-in user, so it requires the
authcapability every starter provides. - Optionally Notifications. Each layout knows where the inbox bell goes when that pack is present.
How a layout stays out of your way
A layout never imports a feature pack, and a feature pack never imports the layout. They meet through four exported
arrays the CLI rebuilds after every install, from what stack.json says each pack contributed:
| Area | Who fills it | Rendered where |
|---|---|---|
navbarActions | Notifications bell, project switcher | The header |
userMenuActions | Organization menu from a SaaS starter | The avatar dropdown |
sidebarActions | Organization switcher block from a SaaS starter | Above the sidebar navigation |
drawerActions | Organization switcher for phones | The foot of the mobile drawer |
That is why the free auth starter, which has no organizations, leaves those areas empty, and why swapping to a SaaS
starter later fills them without touching the layout. Sidebar entries themselves come from nav.constants.ts, which
packs patch with a messageKey so the label translates with everything else.
Admin app
The admin app has a layout too. When you compose, the picker asks once whether to copy the frontend layout for it; say no and you pick separately. All five layouts can render the admin shell. Pin both from the command line with a suffix:
npx @hype-stack/cli compose --packs layout-glass@frontend,layout-basic@adminThemes are separate
A layout is shape and structure. Color comes from the theme you picked at create (or with --theme), and corner
radius from --radius, so the same layout looks different on every project. See Create a Project.
CRM Layout includes its own light/dark palette and pill-control styling in components/layouts/crm-appearance.css. Edit
those tokens to rebrand it. Its nested navigation entries appear as horizontal underline tabs while the primary sidebar
stays visible. Companies and Deals are preview demos; they are not installed as CRM features.
Mobile modals
Mobile forms, settings panels, pickers, and action menus share the layout-owned Modal API in
components/layouts/modal.tsx. Templates set the app-wide presentation in components/layouts/modal.config.ts:
export const modalConfig: { presentation: ModalPresentation } = {
presentation: "drawer",
};Mobile templates default to the bottom drawer, including its slide-up motion, drag handle, and safe-area padding. Vault
and Aether default to centered modals. Set the template's presentation to "modal" to use centered dialogs throughout
the app. Feature code inherits this choice automatically. Use DialogShell for a standard form with a header and
footer, or Modal and ModalContent for a custom body. Both accept an explicit presentation="modal" or
presentation="drawer" on the root when a particular view needs an exception. Destructive confirmations remain behind
the layout's ConfirmShell API.
Environment variables
None.