App Shell

A responsive application frame with optional header, footer, and independently collapsible sidebars.

App Shell provides the stable frame around an application: optional header and footer regions, plus independently controlled left and right sidebars around a scrollable main area.

Variants and behaviour

Pass only the regions the product needs. Each sidebar is optional and can be controlled through its bindable open state, which makes responsive menus and user-controlled panels straightforward.

Customization

Place your own navigation, utilities, and content in the named snippets. The block uses the library’s layout and colour tokens, so it can be re-themed through the same CSS custom properties as the rest of Fractalsvelte.

Examples

Installation

Install Fractalsvelte, then import the block from the dedicated blocks entry point.

Install
pnpm add fractalsvelte
Import
<script lang="ts">
	import { AppShell } from 'fractalsvelte/blocks';
</script>

Usage

Usage
<script lang="ts">
	import { AppShell } from 'fractalsvelte/blocks';
</script>

<AppShell>
	<p>Your application content.</p>
</AppShell>

Props

PropTypeDefaultDescription
headerSnippet-Optional header region.
footerSnippet-Optional footer region.
leftSidebarSnippet-Optional left navigation region.
rightSidebarSnippet-Optional right utility region.
leftOpenbooleantrueWhether the left sidebar is visible.
rightOpenbooleantrueWhether the right sidebar is visible.

Source

Block source is published with the package and can be inspected here.

AppShell.svelte
<script lang="ts">
	import type { Snippet } from 'svelte';
	interface Props { header?: Snippet; footer?: Snippet; leftSidebar?: Snippet; rightSidebar?: Snippet; leftOpen?: boolean; rightOpen?: boolean; children: Snippet; }
	let { header, footer, leftSidebar, rightSidebar, leftOpen = $bindable(true), rightOpen = $bindable(true), children }: Props = $props();
</script>
<div class="app-shell" data-left={Boolean(leftSidebar) && leftOpen} data-right={Boolean(rightSidebar) && rightOpen}>
	{#if header}<header class="app-shell-header">{@render header()}</header>{/if}
	{#if leftSidebar}<aside class="app-shell-left">{@render leftSidebar()}</aside>{/if}
	<main class="app-shell-main">{@render children()}</main>
	{#if rightSidebar}<aside class="app-shell-right">{@render rightSidebar()}</aside>{/if}
	{#if footer}<footer class="app-shell-footer">{@render footer()}</footer>{/if}
</div>
<style lang="sass">
	.app-shell
		display: grid
		grid-template-areas: 'header header header' 'left main right' 'footer footer footer'
		grid-template-columns: 0 minmax(0, 1fr) 0
		min-height: 100%
		transition: grid-template-columns 200ms ease
		&[data-left='true']
			grid-template-columns: minmax(200px, 280px) minmax(0, 1fr) 0
		&[data-right='true']
			grid-template-columns: 0 minmax(0, 1fr) minmax(200px, 280px)
		&[data-left='true'][data-right='true']
			grid-template-columns: minmax(200px, 280px) minmax(0, 1fr) minmax(200px, 280px)
	.app-shell-header, .app-shell-footer
		background: var(--color00)
		border-color: var(--border-primary)
		border-style: solid
		padding: 16px
	.app-shell-header
		border-width: 0 0 1px
		grid-area: header
	.app-shell-footer
		border-width: 1px 0 0
		grid-area: footer
	.app-shell-left, .app-shell-right
		background: var(--color00)
		overflow: auto
		padding: 16px
	.app-shell-left
		border-right: 1px solid var(--border-primary)
		grid-area: left
	.app-shell-right
		border-left: 1px solid var(--border-primary)
		grid-area: right
	.app-shell-main
		grid-area: main
		min-width: 0
		padding: 16px
	@media (max-width: 720px)
		.app-shell, .app-shell[data-left='true'], .app-shell[data-right='true'], .app-shell[data-left='true'][data-right='true']
			grid-template-areas: 'header' 'main' 'footer'
			grid-template-columns: minmax(0, 1fr)
		.app-shell-left, .app-shell-right
			display: none
	@media (prefers-reduced-motion: reduce)
		.app-shell
			transition: none
</style>