Skip to content
Sapphire UIv4.1.0

Layout

Arrange content with shared stack, cluster and column primitives. Use them to control spacing and responsive composition without per-page layout rules.

Examples

Reading

JSX usage

Install this example’s dependencies, load the package stylesheet from Setup, and add Example CSS where shown. The snippet includes its local helpers.

bun add @kongmy-dev/sapphire-ui react react-dom radix-ui
"use client";
import { Button } from "@kongmy-dev/sapphire-ui/button";
import { Callout } from "@kongmy-dev/sapphire-ui/callout";
import { PageSection } from "@kongmy-dev/sapphire-ui/page-section";
import { TableOfContents } from "@kongmy-dev/sapphire-ui/table-of-contents";
import { Toggle } from "@kongmy-dev/sapphire-ui/toggle";
import { Layout } from "@kongmy-dev/sapphire-ui/layout";

function LayoutReading() {
  return (
    <Layout className="docs-layout-demo">
      <PageSection
        label="Field notes"
        heading="A quieter interface"
        subheading="Small decisions that help people read, act and move on."
      >
        <div className="sp-stack">
          <TableOfContents
            items={[
              {
                id: 'reading-rhythm',
                title: 'Build a steady rhythm',
                level: 2,
              },
              { id: 'reading-actions', title: 'Make actions clear', level: 2 },
            ]}
          />
          <article className="sp-prose">
            <h2 id="reading-rhythm">Build a steady rhythm</h2>
            <p>
              Related information belongs together. A consistent inset helps
              readers recognize a group without adding another border or
              heading.
            </p>
            <p>
              Give longer passages room to breathe. Compact controls can sit
              beside generous reading space when each serves a clear purpose.
            </p>
            <ul>
              <li>Align labels and controls.</li>
              <li>Use spacing to show relationships.</li>
              <li>Keep the next action easy to find.</li>
            </ul>
            <h2 id="reading-actions">Make actions clear</h2>
            <p>
              A useful interface makes the ordinary action obvious and the
              destructive action deliberate.{' '}
              <a href="#reading-rhythm">Review the rhythm</a> before adding more
              decoration.
            </p>
          </article>
          <Callout title="Keep the signal">
            Density should remove wasted space while preserving legibility,
            focus and comfortable touch targets.
          </Callout>
          <div className="sp-cluster">
            <Toggle aria-label="Like this article" variant="outline">
              Like
            </Toggle>
            <Button variant="ghost" asChild>
              <a href="#reading-rhythm">Back to the article</a>
            </Button>
          </div>
        </div>
      </PageSection>
    </Layout>
  );
}

export default LayoutReading;
Example CSS

Load these demo styles alongside the package stylesheet for the same layout and surfaces.

.docs-main {
  max-width: none;
}

.docs-flush {
  padding: 0;
  overflow: hidden;
}

.docs-layout-demo {
  --sp-layout-min-height: calc(var(--space-1) * 90);
}

.docs-brand-name {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: calc(var(--space-1) * 7);
}

.docs-brand-suffix {
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: 300;
  line-height: calc(var(--space-1) * 7);
  color: var(--brand);
}

.docs-layout-content {
  padding: var(--space-8) var(--space-6);
}

.docs-layout-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: var(--text-subheading);
  font-weight: 600;
  line-height: calc(var(--space-1) * 7);
  color: var(--foreground-strong);
}

.docs-layout-text {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--muted-foreground);
}

.docs-footer-copy {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--on-dark-muted);
}

.docs-layout-sidebar {
  --sp-layout-sidebar-height: auto;
  --sp-layout-sidebar-width: calc(var(--space-1) * 40);

  align-items: center;
  justify-content: center;
}

.docs-layout-sidebar-label {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
}
HTML markup

Initial rendered HTML only. React handlers and portalled content require the JSX example; static HTML does not reproduce those interactions.

Settings

JSX usage

Install this example’s dependencies, load the package stylesheet from Setup, and add Example CSS where shown. The snippet includes its local helpers.

bun add @kongmy-dev/sapphire-ui react react-dom radix-ui
"use client";
import { useState } from "react";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@kongmy-dev/sapphire-ui/accordion";
import { Button } from "@kongmy-dev/sapphire-ui/button";
import { Card, CardBody, CardFooter, CardHeader } from "@kongmy-dev/sapphire-ui/card";
import { Checkbox } from "@kongmy-dev/sapphire-ui/checkbox";
import { FormField } from "@kongmy-dev/sapphire-ui/form-field";
import { Input } from "@kongmy-dev/sapphire-ui/input";
import { NativeSelect } from "@kongmy-dev/sapphire-ui/native-select";
import { PageSection } from "@kongmy-dev/sapphire-ui/page-section";
import { Layout } from "@kongmy-dev/sapphire-ui/layout";

function LayoutSettings() {
  const [saved, setSaved] = useState(false);
  return (
    <Layout className="docs-layout-demo">
      <PageSection
        heading="Workspace settings"
        subheading="Manage the details your team sees every day."
      >
        <form
          className="sp-stack"
          onSubmit={(event) => {
            event.preventDefault();
            setSaved(true);
          }}
        >
          <Card>
            <CardHeader>
              <h3>General</h3>
              <p>Keep your workspace recognizable.</p>
            </CardHeader>
            <CardBody className="sp-stack">
              <FormField
                label="Workspace name"
                hint="Shown in navigation and invitations."
              >
                <Input defaultValue="Sapphire Studio" />
              </FormField>
              <FormField label="Default language">
                <NativeSelect defaultValue="en">
                  <option value="en">English</option>
                  <option value="ms">Bahasa Melayu</option>
                </NativeSelect>
              </FormField>
              <Checkbox
                label="Send a weekly activity summary"
                description="One email with the changes that matter."
                defaultChecked
              />
            </CardBody>
            <CardFooter>
              <Button type="submit">Save changes</Button>
              <Button type="reset" variant="outline">
                Reset
              </Button>
            </CardFooter>
          </Card>
          <Accordion type="single" collapsible>
            <AccordionItem value="advanced">
              <AccordionTrigger>
                Advanced notification and delivery preferences
              </AccordionTrigger>
              <AccordionContent>
                <Checkbox label="Include detailed audit events" />
              </AccordionContent>
            </AccordionItem>
          </Accordion>
          <output>
            {saved ? 'Settings saved' : 'Your changes stay in this preview.'}
          </output>
        </form>
      </PageSection>
    </Layout>
  );
}

export default LayoutSettings;
Example CSS

Load these demo styles alongside the package stylesheet for the same layout and surfaces.

.docs-main {
  max-width: none;
}

.docs-flush {
  padding: 0;
  overflow: hidden;
}

.docs-layout-demo {
  --sp-layout-min-height: calc(var(--space-1) * 90);
}

.docs-brand-name {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: calc(var(--space-1) * 7);
}

.docs-brand-suffix {
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: 300;
  line-height: calc(var(--space-1) * 7);
  color: var(--brand);
}

.docs-layout-content {
  padding: var(--space-8) var(--space-6);
}

.docs-layout-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: var(--text-subheading);
  font-weight: 600;
  line-height: calc(var(--space-1) * 7);
  color: var(--foreground-strong);
}

.docs-layout-text {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--muted-foreground);
}

.docs-footer-copy {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--on-dark-muted);
}

.docs-layout-sidebar {
  --sp-layout-sidebar-height: auto;
  --sp-layout-sidebar-width: calc(var(--space-1) * 40);

  align-items: center;
  justify-content: center;
}

.docs-layout-sidebar-label {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
}
HTML markup

Initial rendered HTML only. React handlers and portalled content require the JSX example; static HTML does not reproduce those interactions.

Sidebar

JSX usage

Install this example’s dependencies, load the package stylesheet from Setup, and add Example CSS where shown. The snippet includes its local helpers.

bun add @kongmy-dev/sapphire-ui react react-dom
"use client";
import { Layout } from "@kongmy-dev/sapphire-ui/layout";

function LayoutSidebar() {
  return (
    <Layout
      className="docs-layout-demo"
      sidebar={
        <aside className="sp-layout-sidebar docs-layout-sidebar">
          Workspace navigation
        </aside>
      }
    >
      <h2>Workspace</h2>
      <p>The rail and content share the remaining width.</p>
    </Layout>
  );
}

export default LayoutSidebar;
Example CSS

Load these demo styles alongside the package stylesheet for the same layout and surfaces.

.docs-main {
  max-width: none;
}

.docs-flush {
  padding: 0;
  overflow: hidden;
}

.docs-layout-demo {
  --sp-layout-min-height: calc(var(--space-1) * 90);
}

.docs-brand-name {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: calc(var(--space-1) * 7);
}

.docs-brand-suffix {
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: 300;
  line-height: calc(var(--space-1) * 7);
  color: var(--brand);
}

.docs-layout-content {
  padding: var(--space-8) var(--space-6);
}

.docs-layout-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: var(--text-subheading);
  font-weight: 600;
  line-height: calc(var(--space-1) * 7);
  color: var(--foreground-strong);
}

.docs-layout-text {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--muted-foreground);
}

.docs-footer-copy {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--on-dark-muted);
}

.docs-layout-sidebar {
  --sp-layout-sidebar-height: auto;
  --sp-layout-sidebar-width: calc(var(--space-1) * 40);

  align-items: center;
  justify-content: center;
}

.docs-layout-sidebar-label {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
}
HTML markup

Initial rendered HTML only. React handlers and portalled content require the JSX example; static HTML does not reproduce those interactions.

Simple

JSX usage

Install this example’s dependencies, load the package stylesheet from Setup, and add Example CSS where shown. The snippet includes its local helpers.

bun add @kongmy-dev/sapphire-ui react react-dom
"use client";
import { Layout } from "@kongmy-dev/sapphire-ui/layout";

function LayoutSimple() {
  return (
    <Layout
      className="docs-layout-demo"
      header={<header>Workspace header</header>}
      footer={<footer>Workspace footer</footer>}
    >
      <h2>Overview</h2>
      <p>Page content belongs in the main landmark.</p>
    </Layout>
  );
}

export default LayoutSimple;
Example CSS

Load these demo styles alongside the package stylesheet for the same layout and surfaces.

.docs-main {
  max-width: none;
}

.docs-flush {
  padding: 0;
  overflow: hidden;
}

.docs-layout-demo {
  --sp-layout-min-height: calc(var(--space-1) * 90);
}

.docs-brand-name {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: calc(var(--space-1) * 7);
}

.docs-brand-suffix {
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: 300;
  line-height: calc(var(--space-1) * 7);
  color: var(--brand);
}

.docs-layout-content {
  padding: var(--space-8) var(--space-6);
}

.docs-layout-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: var(--text-subheading);
  font-weight: 600;
  line-height: calc(var(--space-1) * 7);
  color: var(--foreground-strong);
}

.docs-layout-text {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--muted-foreground);
}

.docs-footer-copy {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: calc(var(--space-1) * 5);
  color: var(--on-dark-muted);
}

.docs-layout-sidebar {
  --sp-layout-sidebar-height: auto;
  --sp-layout-sidebar-width: calc(var(--space-1) * 40);

  align-items: center;
  justify-content: center;
}

.docs-layout-sidebar-label {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
}
HTML markup

Initial rendered HTML only. React handlers and portalled content require the JSX example; static HTML does not reproduce those interactions.

Props

Generated from the current public TypeScript signatures, including composed behaviour props. “—” means no default is declared in the local signature; forwarded behaviour defaults are not inferred.

Layout

Layout component props
PropTypeRequiredLocal default
headerReactNodeNo—
sidebarReactNodeNo—
footerReactNodeNo—
mainClassNamestring | undefinedNo—
Inherited HTML / React props (280)
Layout common platform props
PropTypeRequiredLocal default
refimport("react").Ref<HTMLDivElement> | undefinedNo—
defaultValuestring | number | readonly string[] | undefinedNo—
classNamestring | undefinedNo—
idstring | undefinedNo—
styleimport("react").CSSProperties | undefinedNo—
tabIndexnumber | undefinedNo—
roleimport("react").AriaRole | undefinedNo—
aria-labelstring | undefinedNo—
childrenReactNodeNo—
onChangeimport("react").ChangeEventHandler<HTMLDivElement, Element> | undefinedNo—

All inherited prop names:

ref key defaultChecked defaultValue suppressContentEditableWarning suppressHydrationWarning accessKey autoCapitalize autoFocus className contentEditable contextMenu dir draggable enterKeyHint hidden id lang nonce slot spellCheck style tabIndex title translate radioGroup role about content datatype inlist prefix property rel resource rev typeof vocab autoCorrect autoSave color itemProp itemScope itemType itemID itemRef results security unselectable popover popoverTargetAction popoverTarget inert inputMode is exportparts part aria-activedescendant aria-atomic aria-autocomplete aria-braillelabel aria-brailleroledescription aria-busy aria-checked aria-colcount aria-colindex aria-colindextext aria-colspan aria-controls aria-current aria-describedby aria-description aria-details aria-disabled aria-dropeffect aria-errormessage aria-expanded aria-flowto aria-grabbed aria-haspopup aria-hidden aria-invalid aria-keyshortcuts aria-label aria-labelledby aria-level aria-live aria-modal aria-multiline aria-multiselectable aria-orientation aria-owns aria-placeholder aria-posinset aria-pressed aria-readonly aria-relevant aria-required aria-roledescription aria-rowcount aria-rowindex aria-rowindextext aria-rowspan aria-selected aria-setsize aria-sort aria-valuemax aria-valuemin aria-valuenow aria-valuetext children dangerouslySetInnerHTML onCopy onCopyCapture onCut onCutCapture onPaste onPasteCapture onCompositionEnd onCompositionEndCapture onCompositionStart onCompositionStartCapture onCompositionUpdate onCompositionUpdateCapture onFocus onFocusCapture onBlur onBlurCapture onChange onChangeCapture onBeforeInput onBeforeInputCapture onInput onInputCapture onReset onResetCapture onSubmit onSubmitCapture onInvalid onInvalidCapture onLoad onLoadCapture onError onErrorCapture onKeyDown onKeyDownCapture onKeyPress onKeyPressCapture onKeyUp onKeyUpCapture onAbort onAbortCapture onCanPlay onCanPlayCapture onCanPlayThrough onCanPlayThroughCapture onDurationChange onDurationChangeCapture onEmptied onEmptiedCapture onEncrypted onEncryptedCapture onEnded onEndedCapture onLoadedData onLoadedDataCapture onLoadedMetadata onLoadedMetadataCapture onLoadStart onLoadStartCapture onPause onPauseCapture onPlay onPlayCapture onPlaying onPlayingCapture onProgress onProgressCapture onRateChange onRateChangeCapture onSeeked onSeekedCapture onSeeking onSeekingCapture onStalled onStalledCapture onSuspend onSuspendCapture onTimeUpdate onTimeUpdateCapture onVolumeChange onVolumeChangeCapture onWaiting onWaitingCapture onAuxClick onAuxClickCapture onClick onClickCapture onContextMenu onContextMenuCapture onDoubleClick onDoubleClickCapture onDrag onDragCapture onDragEnd onDragEndCapture onDragEnter onDragEnterCapture onDragExit onDragExitCapture onDragLeave onDragLeaveCapture onDragOver onDragOverCapture onDragStart onDragStartCapture onDrop onDropCapture onMouseDown onMouseDownCapture onMouseEnter onMouseLeave onMouseMove onMouseMoveCapture onMouseOut onMouseOutCapture onMouseOver onMouseOverCapture onMouseUp onMouseUpCapture onSelect onSelectCapture onTouchCancel onTouchCancelCapture onTouchEnd onTouchEndCapture onTouchMove onTouchMoveCapture onTouchStart onTouchStartCapture onPointerDown onPointerDownCapture onPointerMove onPointerMoveCapture onPointerUp onPointerUpCapture onPointerCancel onPointerCancelCapture onPointerEnter onPointerLeave onPointerOver onPointerOverCapture onPointerOut onPointerOutCapture onGotPointerCapture onGotPointerCaptureCapture onLostPointerCapture onLostPointerCaptureCapture onScroll onScrollCapture onScrollEnd onScrollEndCapture onWheel onWheelCapture onAnimationStart onAnimationStartCapture onAnimationEnd onAnimationEndCapture onAnimationIteration onAnimationIterationCapture onToggle onBeforeToggle onTransitionCancel onTransitionCancelCapture onTransitionEnd onTransitionEndCapture onTransitionRun onTransitionRunCapture onTransitionStart onTransitionStartCapture

ARIA attributes and native event handlers keep their React types. data-* attributes are also accepted by React.

Setup

bun add @kongmy-dev/sapphire-ui react react-dom

For an app using package components throughout, load the full stylesheet once. Add this component import where you use it.

import "@kongmy-dev/sapphire-ui/sapphire.css";
import { Layout } from "@kongmy-dev/sapphire-ui/layout";

Mixing package and vendored components? Follow application setup.

Search documentation