Summit
Foundations

Layout

Layout sets the breakpoints, the fixed measures of the app shell, and the width of each page and overlay.

Breakpoints

The breakpoints are Tailwind v4’s. A breakpoint is a minimum viewport width.

BreakpointMinimum WidthChange in a Component
sm640pxA Dialog, an Alert Dialog, and a side Sheet or Drawer change to their fixed widths. A Dialog’s footer becomes a row.
md768pxThe Sidebar docks next to the page. The text of an Input, a Textarea, and a Native Select changes from 16px to 14px.
lg1024pxNone
xl1280pxNone
2xl1536pxNone

See Typography for the rule on the 16px text of a field below md.

Shell Measures

The app shell is a Sidebar, a header, and the page. SidebarProvider sets the Sidebar’s two widths as variables.

PartSizeSource
Sidebar256px wide--sidebar-width, 16rem
Sidebar collapsed to its icons48px wide--sidebar-width-icon, 3rem
Sidebar below 768px75% of the screen, and at most 384px from 640pxThe Sheet it opens in
Header48px tallh-12 on the header inside SidebarInset

Overlay Widths

An overlay has a fixed width from 640px. A Sheet or a Drawer on the top or the bottom edge spans the screen.

OverlayWidth
Dialog576px from 640px. Below that, the screen less 16px on each side.
Alert Dialog384px from 640px. 320px below that, and at size="sm".
Sheet or Drawer on the left or the right384px from 640px. Below that, 75% of the screen.
Popover288px
Hover Card256px
TooltipAt most 320px
A toast from Sonner356px from 640px. At 600px or less, the screen less 16px on each side.
Toast384px, or the screen less 16px on each side if that is narrower

Page Widths

A page has one of three widths, depending on its content. In the figure, each frame is a 1440px screen at one tenth of its size.

DataNo class
Defaultmax-w-7xl
Form or readingmax-w-2xl
PageClassWidthPurpose
DataNoneThe full widthA Table or a dashboard
Defaultmax-w-7xl1280pxAll other pages, and the marketing site
Form or readingmax-w-2xl672pxA form or a long text

Rules

The Sidebar already follows the first rule. Apply the other two to a page built inside the shell.

Shell Breakpoint

The shell has one viewport breakpoint. Below 768px, the Sidebar is removed from the page and opens in a Sheet. From 768px, it is docked next to the page. Avoid switching the shell at another width: useMobile and the Sidebar’s md: classes both use 768px, and a second width creates a range where one reports a phone and the other a desktop.

Container Queries

Inside the shell, make a component respond to the width of its container. A Field with orientation="responsive" does: it places its label next to its control when its field group is at least 448px wide. On one screen, the same form then has one layout in a 576px Dialog and another in a 384px Sheet.

Card Grids

Summit has no column grid. Use equal tracks with grid-cols-* and one gap-4, and change the number of tracks at a breakpoint. For example, grid gap-4 sm:grid-cols-2 xl:grid-cols-4 places one Card in a row below 640px, two from 640px, and four from 1280px.

Accessibility

A page must work at a width of 320 CSS pixels without scrolling in two directions, the floor of WCAG 2.2 SC 1.4.10 Reflow. That width is a 1280px window at 400% zoom. Three mechanisms address this.

  • A class with no prefix is the layout at 320px because every breakpoint is a minimum width.
  • An overlay stays inside the screen. On a 320px screen, a Dialog is 288px wide.
  • A Table wraps itself in a container that scrolls sideways, so wide columns scroll inside it and the page around it reflows. The criterion exempts a data table.

No component reads the screen’s orientation, so a layout depends on width alone. This addresses WCAG 2.2 SC 1.3.4 Orientation.

In Code

To apply a class from a viewport width, prefix it with a breakpoint. To apply it from a container’s width, mark the container with @container and prefix the class with @, as in @md:flex-row.

<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
    <Card />
    <Card />
    <Card />
    <Card />
</div>

useMobile

useMobile reports whether the viewport is narrower than 768px. It is the shell’s breakpoint in script, and the Sidebar reads it to open in a Sheet.

useMobile()
false
npx shadcn@latest add @summit/use-mobile

The CLI also adds the hook when you add @summit/sidebar.

import { useMobile } from '@/hooks/use-mobile';

function Filters() {
    const mobile = useMobile();

    return mobile ? <FiltersDrawer /> : <FiltersPopover />;
}

The hook has no arguments, and its width is fixed. It listens to a media query, so a component renders again when the viewport crosses 768px, not on every resize. On the server, it returns false, so the first render on a phone is the desktop branch. Hide that branch below md with a class, as the Sidebar does with hidden md:block.

Use a class when a class can do the work. md:hidden is already correct in the server’s HTML, and the hook is correct only after the page’s scripts run. Use the hook when the markup itself differs, as the Sidebar’s does.

On this page