Skip to content
portalbits
Home Docs GitHub(opens in a new tab)

Guides

Get startedFoundationsMotion

Components

All components

Buttons

ButtonIconButton

Inputs

InputSelectTextareaSlider

Selection

CheckboxToggleToggleGroupSwitchRadioGroup

Identity

AvatarBadge

Overlays

DialogPopoverTooltipDropdownMenuAlertDialog

Disclosure

TabsAccordion

Cards

AppCardArticleCardFeatureCard

Content

CarouselSectionHeaderEmptyStateAlertProgressSeparatorRowListRow

Chrome

PortalShellSidebarNavMobileNavTopBarAuthFrameColorSelectorFloatingNav

PortalShell

The application frame: sidebar, toolbar, and page content.

The shell is the application frame. Open it on its own so the sidebar, toolbar, and mobile navigation can use the viewport.

Open the shell

Source

<script lang="ts">
  import 'portal-bits/styles.css';
  import 'portal-bits/tokens.css';
  import type { Snippet } from 'svelte';

  let { children }: { children: Snippet } = $props();
</script>

{@render children()}
<script lang="ts">
  import PortalShellExample from './PortalShellExample.svelte';
</script>

<PortalShellExample />
<script lang="ts">
  import { Button, PortalShell, TopBar, type NavItem } from 'portal-bits';
  // This example uses @lucide/svelte for the navigation icons. Omit `icon` to skip it.
  import { Component, Layers, LayoutGrid } from '@lucide/svelte';

  const items: NavItem[] = [
    { href: '#overview', label: 'Overview', icon: LayoutGrid },
    { href: '#foundations', label: 'Foundations', icon: Layers },
    { href: '#components', label: 'Components', icon: Component, count: '29' },
  ];
  // The consumer supplies the active URL. In SvelteKit, use the current pathname.
  let active = $state('#overview');
  let collapsed = $state(false);
</script>

<PortalShell {items} {active} brandHref="#overview" brandLabel="Example Studio" bind:collapsed>
  {#snippet brand()}<span>Example Studio</span>{/snippet}
  {#snippet summary()}
    <p>A familiar feel.<br />An open foundation.</p>
  {/snippet}
  {#snippet footer()}
    <Button variant="quiet" onclick={() => (collapsed = !collapsed)}>
      {collapsed ? 'Expand' : 'Collapse'} sidebar
    </Button>
  {/snippet}
  {#snippet topbar()}
    <TopBar breadcrumb="Discover" />
  {/snippet}
  <section>
    <h1>Overview</h1>
    <p>Your page content goes here.</p>
  </section>
</PortalShell>

API reference

These tables list the props and snippets that PortalShell declares. Attributes passed through to an element or primitive are listed under Forwarded attributes, not here.

Props

NameTypeDefaultRequiredBindingDescription
itemsNavItem[]NoneYesOne-wayNavigation items as `{ href, label, icon?, count? }`. The sidebar and mobile bar share them.
activestringNoneYesOne-wayThe `href` of the current item. Compared with exact equality.
collapsedbooleanfalseNoBindableWhether the sidebar shows icons only.
brandHrefstring/NoOne-wayThe destination of the brand link.
brandLabelstringNoneNoOne-wayThe brand link name. Without it, the visible brand names the link, or "Home" when collapsed.

Snippets

NameTypeDefaultRequiredBindingDescription
brandSnippetNoneNoOne-wayReplaces the default "portalbits" wordmark.
summarySnippetNoneNoOne-wayContent under the brand. Hidden when collapsed.
footerSnippetNoneNoOne-wayContent at the foot of the sidebar. Hidden when collapsed.
topbarSnippetNoneNoOne-wayThe toolbar above the page content.
childrenSnippetNoneNoOne-wayThe page content, inside `main`.

Forwarded attributes

None. PortalShell forwards no attributes.

No data attributes are documented as stable.

Limits

  • The consumer supplies `active`. The shell reads no URL.
  • The skip link targets a fixed `main` with the id `main-content`.
  • The mobile navigation appears at narrow widths. That breakpoint is an adaptation.
  • The shell needs the full viewport. The gallery links to it instead of embedding it.

Related

  • SidebarNav
  • MobileNav
  • TopBar
Previous Row Next SidebarNav

On this page

  • Preview
  • Source
  • API reference
  • Props
  • Snippets
  • Forwarded attributes
  • Limits