App shell
The page around a chat — a sidebar beside, a header above, the chat in the rest — as a grid you size once.
-
.aparte-app-header
On this page
<!-- Three pieces: the grid and the header are recipes, the sidebar is an element
because it has behaviour. The toggle in the header needs no script — the sidebar
listens for `data-aparte-sidebar-toggle` itself, and shows under 48rem. This frame is
narrower than 48rem, so the sidebar is a drawer behind that toggle; open the demo on
its own page and the window is wide enough for the column. -->
<div class="aparte-app-shell" style="height: 24rem">
<aparte-sidebar collapsed>
<div class="aparte-sidebar__header">
<span class="aparte-sidebar__brand">aparté</span>
<button class="aparte-btn aparte-btn--icon aparte-btn--sm" type="button" aria-label="New chat">
<aparte-icon name="edit"></aparte-icon>
</button>
<!-- The header's toggle shows only while the sidebar is closed (or under 48rem):
on a wide window, this one is what closes it. -->
<button class="aparte-btn aparte-btn--icon aparte-btn--sm" type="button" aria-label="Toggle the sidebar" data-aparte-sidebar-toggle>
<aparte-icon name="menu"></aparte-icon>
</button>
</div>
<div class="aparte-sidebar__search aparte-field-group">
<input class="aparte-field aparte-field--sm" type="search" placeholder="Search conversations" data-aparte-sidebar-search>
</div>
<div class="aparte-sidebar__body">
<aparte-conversation-list active-id="c1"></aparte-conversation-list>
</div>
<div class="aparte-sidebar__footer">
<span class="aparte-avatar aparte-avatar--sm">P</span> Paul
</div>
</aparte-sidebar>
<header class="aparte-app-header">
<button class="aparte-btn aparte-btn--icon aparte-app-header__toggle" type="button" aria-label="Toggle the sidebar" data-aparte-sidebar-toggle><aparte-icon name="menu"></aparte-icon></button>
<span class="aparte-app-header__title">Deploy checklist</span>
<div class="aparte-app-header__actions"><span class="aparte-tag">gpt-4.1</span></div>
</header>
<main class="aparte-app-shell__main">
<aparte-chat>
<aparte-chat-viewport>
<aparte-chat-bubble message-id="u1" data-role="user" content="Where does the sidebar's state live?"></aparte-chat-bubble>
<aparte-chat-bubble message-id="a1" data-role="assistant" name="Assistant" content="On the element: collapsed is an attribute you can set, read and persist."></aparte-chat-bubble>
</aparte-chat-viewport>
<aparte-composer>
<div class="aparte-composer-shell">
<div class="aparte-composer-row">
<aparte-composer-input></aparte-composer-input>
<aparte-composer-send></aparte-composer-send>
</div>
</div>
</aparte-composer>
</aparte-chat>
</main>
</div>
<script>
const day = 864e5;
document.querySelector('aparte-conversation-list').conversations = [
{ id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },
{ id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - day },
{ id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },
{ id: 'c4', title: 'The first release', updatedAt: Date.now() - 60 * day },
];
</script>Most chat products have the same page: the conversations in a column on the left, a bar with the
conversation’s title on top, the chat filling the rest. .aparte-app-shell is that page as a
grid of two columns and two rows. The sidebar spans both rows; the header
and .aparte-app-shell__main stack in the second column.
Size the shell (height: 100dvh for a whole page) and everything inside follows: the main area
gives the chat the height it needs to scroll. Under 48rem of window, the
sidebar leaves the grid and becomes a drawer behind the header’s button.
This frame is narrower than that, which is why the sidebar is a drawer here; open the demo on its
own page to see the column.
<div class="aparte-app-shell" style="height: 100dvh">
<aparte-sidebar>…</aparte-sidebar>
<header class="aparte-app-header">…</header>
<main class="aparte-app-shell__main">
<aparte-chat></aparte-chat>
</main>
</div>The shell is classes and nothing else: no element, no script. The behaviour lives in the parts
that have some: <aparte-sidebar> folds and becomes a drawer, <aparte-split> drags. The main
area takes a split as well as a chat, for a chat beside a preview.
The Layouts guide builds a whole page, and shows the other shapes a chat takes: a reading column, a side panel, a floating composer, a centred welcome.
Classes
Section titled “Classes”.aparte-app-header · .aparte-app-shell · .aparte-app-shell__main · .aparte-sidebar · .aparte-split
Accessibility
Section titled “Accessibility”- The shell styles whatever elements carry its classes, so the landmarks are yours to choose: an
<aside>or<aparte-sidebar>, a<header>, a<main>give a screen reader the page’s regions.