component — layout

Panel

A tinted band that sets a region of the page apart — a fill and padding, with no border, radius or shadow. Muted for grouping, brand for the one band that asks for something; bleed runs it through a Container's gutter to the edges.

  • layout
  • surface
  • section
  • band
  • background

use a panel when

  • A page section that needs more separation than whitespace and a heading give it — a settings group, a FAQ, a footer region.
  • Alternating bands down a long page, so the reader can see where one section ends.
  • The single call to action on a page, with tone="brand".

don't

  • Don't use a Panel for a self-contained unit beside others of a different kind. That is a Card.
  • Don't put a Panel in a Card or a Card-shaped surface. Inside a card, a Heading and a Separator separate the parts.
  • Don't give a Panel a border or a radius. The moment it needs an edge it is a Card.
  • Don't reach for a Panel when whitespace would do. Every section in a tint is the same as none of them.

05 examples

Default

A raised band between two stretches of page. No edge and no corner: the ground is the whole separation.

The page runs on the paper, under its own headings.

frequently asked

A region the reader should see as one piece, set apart without drawing a box round it.

And carries on below it.

Brand tone

One step deeper, for the one band on the page that is asking for something.

move your team to pro.

Unlimited projects, audit log and SSO.

Two-column settings

Label and help text on the left, controls on the right, rows divided by a Separator. The Panel groups the rows; nothing inside it needs a surface of its own.

workspace

name

Shown in the sidebar and in invitations.

public profile

Anyone with the link can see the workspace's projects.

Bleed inside a Container

`bleed` pulls the band out through the Container's gutter and pads its content back in by the same amount, so the text inside starts where the text outside does.

The column's text starts here.

So does the band's, while its fill runs to the Container's edges.

Across the window

For a band the width of the window, put the Panel outside the Container and the Container inside it — the fill spans everything and the content keeps to the column.

release notes

The content sits in the column; only the fill is full width.

what to use instead

by job

By what the panel was doing.

Separating sections
A self-contained unit
A callout

source

panel.tsx

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

import { cn } from "@/lib/utils"

/**
 * Panel — quebi design system
 *
 * A tinted band that sets a region of the page apart: a fill and padding, and
 * nothing else. No border, no radius, no shadow — the tint is the whole
 * separation, which is what makes it the right surface for a page section and
 * the wrong one for a self-contained unit (that is a Card).
 *
 * It takes the width of its parent. Two ways to run it edge to edge:
 *
 * - Outside a `Container`, with a `Container` inside it, for a band across the
 *   whole window whose content still lines up with the column around it.
 * - `bleed` inside a `Container`: the band pulls out through the Container's
 *   gutter (`--container-padding`) and pads its content back in by the same
 *   amount, so the text inside starts where the text outside does. Outside a
 *   Container the variable is unset and `bleed` does nothing.
 *
 * The muted tone is the raised ground (`bg-quebi-raised`), the inset band the
 * rest of the system uses. `tone="brand"` is one step deeper
 * (`bg-quebi-pressed`), for the one band on a page that is asking for
 * something (a call to action, an upgrade) — emphasis by ink, not by hue.
 * Both grounds flip with the theme, so the band reads as recessed in either.
 */
export interface PanelProps extends React.HTMLAttributes<HTMLElement> {
  /** `muted` for grouping, `brand` for the single band that carries the ask. */
  tone?: "muted" | "brand"
  /** Pull the band through the enclosing Container's gutter to its edges. */
  bleed?: boolean
  /** The element to render. `section` when the band is a region with its own
   * heading, `aside` when it is tangential to the page. */
  as?: "div" | "section" | "aside"
}

export function Panel({
  className,
  tone = "muted",
  bleed = false,
  as: Element = "div",
  ...props
}: PanelProps) {
  return (
    <Element
      data-slot="panel"
      data-tone={tone}
      className={cn(
        "py-8 text-quebi-fg",
        tone === "brand" ? "bg-quebi-pressed" : "bg-quebi-raised",
        bleed
          ? "-mx-(--container-padding) px-[var(--container-padding,calc(var(--spacing)*6))]"
          : "px-6",
        className,
      )}
      {...props}
    />
  )
}