mica
mica

Example

Surface treatment
Preview width

Try the panel button, nested Projects links, workspace switcher, and account control. Desktop icon collapse is explicitly enabled in this example. On mobile, both treatments use the same flush panel with a compact close control. Icons are authored SVG examples, not a dependency or library requirement.

Open docked example · Open inset example

Basic usage — no icons required

This text-only example stays expanded on desktop and becomes a modal panel on narrow containers. Name that modal with dialog-label on the panel; the enhancement turns the panel itself into the modal, without moving it.

import { Sidebar, SidebarClose, SidebarItem, SidebarLayout, SidebarMain, SidebarToggle } from "@akonwi/mica/react";

// <SidebarLayout> loads sidebar.js on the client and wires every toggle
// to the panel by sidebarId.
<SidebarLayout variant="docked" sidebarId="app-sidebar">
  <Sidebar dialogLabel="Workspace navigation">
    <header role="presentation">
      <SidebarItem href="/">Your app</SidebarItem>
      <SidebarClose aria-label="Close navigation">×</SidebarClose>
    </header>
    <nav aria-label="Workspace">
      <section>
        <h2>Workspace</h2>
        <SidebarItem href="/overview" aria-current="page">Overview</SidebarItem>
        <SidebarItem href="/projects">Projects</SidebarItem>
      </section>
    </nav>
    <footer role="presentation"><SidebarItem href="/account">Account</SidebarItem></footer>
  </Sidebar>
  <SidebarMain>
    <header>
      <SidebarToggle aria-label="Toggle navigation">Navigation</SidebarToggle>
    </header>
    <main>…page content…</main>
  </SidebarMain>
</SidebarLayout>
<script type="module" src="sidebar.js"></script>
<m-sidebar-layout variant="docked">
  <m-sidebar id="app-sidebar" dialog-label="Workspace navigation">
    <header role="presentation">
      <a data-sidebar-item href="/">Your app</a>
      <button type="button" data-sidebar-close aria-label="Close navigation">×</button>
    </header>
    <nav aria-label="Workspace">
      <section>
        <h2>Workspace</h2>
        <a data-sidebar-item href="/overview" aria-current="page">Overview</a>
        <a data-sidebar-item href="/projects">Projects</a>
      </section>
    </nav>
    <footer role="presentation"><a data-sidebar-item href="/account">Account</a></footer>
  </m-sidebar>
  <div data-sidebar-main>
    <header>
      <button type="button" data-sidebar-toggle
              aria-controls="app-sidebar" aria-label="Toggle navigation">Navigation</button>
    </header>
    <main>…page content…</main>
  </div>
</m-sidebar-layout>

API

Layout attributes

NameDefaultDescription
variant="docked | inset"dockedOn m-sidebar-layout. Docked separates panel and content with one edge. Inset puts a thin square frame around the content. Both become flush on mobile.
collapse="icon"omittedOpt-in desktop icon rail. Without it, the desktop panel stays expanded and the trigger appears only on mobile. Requires a named icon or avatar on each top-level item. Invalid anatomy keeps the panel expanded and the desktop toggle visible but unavailable, with an explanatory title and console diagnostic. Mobile navigation remains available.
persist="false"enabledOpt out of reading and writing the desktop collapse preference cookie.
aria-current="page"—Application-owned state on the current link. Stronger text and a quiet background, with no border or shadow.

Content/markup contract

PartContractDescription
PanelDirect m-sidebar, first childUnique ID, and dialog-label: the accessible name of the panel when it is a modal on mobile. Contains optional header/footer and a named native nav. Header/footer stay fixed; navigation scrolls. The panel itself is inert unless used in an enhanced layout. Nothing is moved, so the panel and main surface may be replaced or re-rendered; the layout re-enhances. (An empty dialog[data-sidebar-dialog] from earlier versions is still accepted; its aria-label names the panel when dialog-label is absent.)
Main surfaceDirect [data-sidebar-main]Consumer-authored toolbar and main content. Scrolls independently inside the layout's height.
Togglebutton[data-sidebar-toggle] in the main surfaceUse type="button", a stable accessible label, and aria-controls matching the panel ID. The module manages aria-expanded. Multiple matching toggles are supported.
Closebutton[data-sidebar-close] in the panel headerAuthor the content and accessible name. Visible beside the workspace control only on mobile.
Item[data-sidebar-item] on a native link, button, or summaryShared spacing and presentation. Navigation remains ordinary links; controls retain native behavior.
Groupnav > section > h2Optional section label. Label each nav region to distinguish it from other navigation.
Nested linksdetails > summary[data-sidebar-item] and [data-sidebar-subnav]Use a subnav wrapper after summary. Direct anchors get the nested style. Clicking a collapsed rail summary expands the panel and opens that details group.

An m-avatar inside a named sidebar item counts as its collapsed visual without data-sidebar-icon. Set --avatar-size on the avatar to size it; keep the accompanying text in data-sidebar-label.

Optional icon-rail anatomy

AttributeTargetDescription
data-sidebar-iconAuthored icon, image, or text markOnly sizes and aligns the supplied content. Mica ships no sidebar icon set. Use aria-hidden="true" for decorative icons.
data-sidebar-labelItem's text wrapperHidden in icon mode. Every top-level item must have its own aria-label or aria-labelledby and either a data-sidebar-icon visual or m-avatar outside its hidden label to enable the rail. A title can supplement discoverability.
data-sidebar-badgeOptional count or metadataAligned at the end; hidden in icon mode.
data-sidebar-chevronOptional authored disclosure indicatorHidden in icon mode. A direct summary indicator rotates when its native details opens.

Styling

Set these properties on m-sidebar-layout. Semantic surface, text, and border colors come from the normal mica theme.

PropertyDefaultDescription
--sidebar-width15.5remExpanded desktop panel width.
--sidebar-rail-width3.75remCollapsed desktop width.
--sidebar-mobile-width18remMobile panel width, clamped to leave an outside-dismiss area.
--sidebar-height100svhLayout height. Set a bounded height for an embedded workspace.
--sidebar-padvar(--space-xs)Panel padding; mobile safe-area insets are respected.
--sidebar-icon-size1remDefault icon box. Avatars retain their own --avatar-size, including when marked with data-sidebar-icon.
--sidebar-font-size0.875remItem text.
--sidebar-group-size0.6875remGroup label text.
--sidebar-subnav-size0.8125remNested link and metadata text.
--sidebar-active-weight600Current-link text weight.
--sidebar-close-size2.75remMobile close target.

Enhancement and accessibility

Import sidebar.js to register m-sidebar-layout. The module enhances valid authored markup, adds no icons or links, and has no shared runtime. Below 44rem layout width, the panel becomes a modal in place: shown in the top layer over a scrim, with role="dialog", aria-modal, and the rest of the layout inert while it is open, so focus stays in the panel. Only the layout's own children are made inert: keep page content inside the layout, as an app shell does. Escape closes it unless a popover inside the panel is open. Clicking outside or following a link also closes the panel, including a link a client-side router prevents. Modified clicks keep their normal behavior.

Desktop collapse state is remembered automatically in a cookie named mica-sidebar- followed by the URL-encoded m-sidebar ID. Keep that ID stable across pages to share the preference; use different IDs for independent sidebars. The cookie stores collapsed or expanded, lasts one year from the last change, and applies across the origin (Path=/, SameSite=Lax, and Secure on HTTPS). Restoration happens when the module connects. Mobile drawers still start closed, and opening or closing them does not change the desktop preference. Invalid collapse anatomy stays expanded without overwriting the cookie. If cookies are unavailable, the sidebar continues to work without persistence.

Set persist="false" on m-sidebar-layout to disable persistence. This leaves any existing cookie untouched; it will be read again on a future connection with persistence enabled.

The layout exposes open(), close(), and toggle(). On mobile they open and close the panel; on desktop they expand/collapse the optional rail. Without icon collapse, desktop close() and toggle() leave the panel expanded. Router links need no extra code: the panel closes on the click even when the router prevents it. To act on a link without closing, use a button. Mica does not choose the active page or emit navigation events.

The layout fires a bubbling m-on-toggle event whenever the panel's state or mode changes, and once when enhancement first resolves it. event.detail.expanded is true when the mobile panel is open, or when the desktop panel is expanded rather than a rail. event.detail.mobile is true below the mobile width. Toggles and close buttons are found when clicked, so ones rendered or replaced after enhancement (a re-rendered page header, for example) work and receive aria-expanded.

Without the import, the sidebar remains visible, native links and details work, and inert toggle/close buttons stay hidden. At narrow viewport widths the unenhanced layout stacks its sidebar above content. Missing required controls also leave this fallback intact.

Use a native page header for the banner. Panel header/footer are internal grouping: role="presentation" avoids duplicate page landmarks when they are not nested in a sectioning element. Do not add application-menu roles to navigation links. Keep popovers owned by sidebar controls inside m-sidebar so they move with it and remain usable inside the mobile modal.

← bottom navigationdata table →