npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gnome-ui/layout

v1.22.1

Published

Full-page layout shell components following the GNOME Human Interface Guidelines

Readme

@gnome-ui/layout

Full-page application shell components following the GNOME Human Interface Guidelines, built on the Adwaita design language.

npm CI Storybook License: MIT

Live documentation: Storybook →

Installation

npm install @gnome-ui/layout

Setup

Import the stylesheet once at the root of your app:

// main.tsx or App.tsx
import "@gnome-ui/layout/styles";

Formatting

Numeric layout components such as CounterCard and StatCard inherit locale and default number formatting from GnomeProvider in @gnome-ui/react.

import { GnomeProvider } from "@gnome-ui/react";
import { CounterCard } from "@gnome-ui/layout";

<GnomeProvider
  locale="en-US"
  numberFormat={{ notation: "compact", compactDisplay: "short" }}
>
  <CounterCard label="Downloads" value={12500} />
</GnomeProvider>

Use compact notation for values like 13K; omit numberFormat or use notation: "standard" for full values like 12,500. A component-level format prop, where available, still takes precedence.

Tree-shaking

The package ships per-component entry points, so bundlers can eliminate unused components automatically:

// Only Layout is included in the bundle
import { Layout } from "@gnome-ui/layout";

Per-component paths are also available:

import { Layout } from "@gnome-ui/layout/components/Layout";

Components

Layout

Full-page application shell with four named, optional zones:

| Zone | Prop | Description | |------|------|-------------| | Header | header / topBar | Pinned header — typically a Toolbar, HeaderBar, or app header composition. Never scrolls when scroll="content". | | Sidebar | sidebar | Fixed-width lateral navigation — typically a Sidebar. | | Content | children | Scrollable main area. Fills remaining space. | | Footer | footer / bottomBar | Pinned footer — typically a status Toolbar. Never scrolls when scroll="content". |

All props of <div> are forwarded to the root element.

topBar and bottomBar remain supported for existing apps. New code can use the shell-style aliases header and footer; the legacy names take precedence when both are provided.

Height and scroll modes

| Prop | Type | Default | Description | |------|------|---------|-------------| | height | "viewport" \| "parent" | "viewport" | viewport fills the browser viewport (100vh); parent fills the containing element (100%) for nested layouts. | | scroll | "content" \| "page" \| "none" | "content" | content scrolls only the main area; page lets the whole shell scroll; none disables internal scrolling. |

Mobile sidebar overlay

On narrow viewports the sidebar becomes a slide-in overlay panel. The default breakpoint is 400 px, matching GNOME split-view behaviour.

| Prop | Type | Default | Description | |------|------|---------|-------------| | sidebarOpen | boolean | false | Whether the sidebar overlay is visible on mobile | | onSidebarClose | () => void | — | Called when the user taps the backdrop — use it to set sidebarOpen back to false | | onSidebarOpenChange | (open, reason) => void | — | Called for shell-driven open changes such as backdrop and Escape | | sidebarPlacement | "start" \| "end" | "start" | Places the sidebar on the leading or trailing edge | | sidebarLabel | string | — | Accessible label for the sidebar landmark wrapper | | sidebarBreakpoint | "narrow" \| "medium" \| "wide" | "narrow" | Overlay threshold: 400, 550, or 860 px | | sidebarCollapseMode | "none" \| "rail" \| "overlay" | "none" | Wide-layout collapse behaviour | | sidebarCollapsed | boolean | false | Applies shell-level collapsed sidebar styling | | sidebarCollapsedWidth | number | 56 | Rail width in pixels |

On wider viewports the sidebar stays in layout flow unless sidebarCollapseMode="overlay" and sidebarCollapsed are both set.

When the sidebar overlay is open, focus moves into the sidebar, Tab and Shift+Tab remain inside it, and Escape requests close through onSidebarOpenChange(false, "escape"). Add sidebarLabel when the sidebar content does not provide a labelled nav.

import { useState } from "react";
import {
  AppHeader,
  Layout,
  PageContent,
  SidebarShell,
  SidebarTrigger,
  StatusBar,
} from "@gnome-ui/layout";
import "@gnome-ui/layout/styles";
import { SidebarSection, SidebarItem, Text } from "@gnome-ui/react";

export default function App() {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);

  return (
    <Layout
      header={
        <AppHeader
          title="My App"
          leading={
            <SidebarTrigger
              sidebarOpen={sidebarOpen}
              sidebarCollapsed={sidebarCollapsed}
              onSidebarOpenChange={setSidebarOpen}
              onSidebarCollapsedChange={setSidebarCollapsed}
            />
          }
        />
      }
      sidebar={
        <SidebarShell
          header={<Text variant="heading">My App</Text>}
          collapsed={sidebarCollapsed}
        >
          <SidebarSection>
            <SidebarItem label="Home" active onClick={() => setSidebarOpen(false)} />
            <SidebarItem label="Settings" onClick={() => setSidebarOpen(false)} />
          </SidebarSection>
        </SidebarShell>
      }
      sidebarOpen={sidebarOpen}
      sidebarCollapsed={sidebarCollapsed}
      sidebarCollapseMode="rail"
      onSidebarClose={() => setSidebarOpen(false)}
      footer={
        <StatusBar>
          <Text variant="caption" color="dim">Ready</Text>
        </StatusBar>
      }
    >
      <PageContent as="section" maxWidth="lg">
        <Text variant="title-2">Welcome</Text>
      </PageContent>
    </Layout>
  );
}

Shell API improvements

The current shell API adds a few improvements over the original topBar/bottomBar composition:

  • header and footer aliases make shell regions read naturally while keeping topBar and bottomBar compatible with existing apps.
  • height="parent" makes nested shells possible without accidental double 100vh layouts.
  • scroll="content" keeps header/footer/sidebar fixed while only the main area scrolls.
  • sidebarBreakpoint, sidebarPlacement, and sidebarCollapseMode cover narrow overlays, right-side panels, and icon-only rail sidebars.
  • SidebarTrigger coordinates the same button with overlay open state on narrow screens and rail collapse on wider screens.
  • Overlay sidebars move focus inside, trap Tab/Shift+Tab, close with Escape, and restore focus to the previous trigger.

SidebarShell

Full-height sidebar composition for Layout.sidebar. It wraps the GNOME Sidebar with optional fixed header/footer areas and a scrollable navigation middle.

| Prop | Type | Description | |------|------|-------------| | header | ReactNode | Fixed content above the navigation list. | | children | ReactNode | Navigation content, usually SidebarSection children. | | footer | ReactNode | Fixed content below the navigation list. |

All Sidebar props such as collapsed, searchable, filter, mode, and variant pass through.

import { SidebarShell } from "@gnome-ui/layout";
import { SidebarSection, SidebarItem, Text } from "@gnome-ui/react";

<SidebarShell header={<Text variant="heading">Files</Text>} searchable>
  <SidebarSection>
    <SidebarItem label="Home" active />
    <SidebarItem label="Starred" />
  </SidebarSection>
</SidebarShell>

SidebarTrigger

Header button that toggles the sidebar using the current layout mode. On overlay breakpoints it opens or closes the panel; on wider screens it toggles rail collapse.

| Prop | Type | Description | |------|------|-------------| | sidebarOpen | boolean | Current overlay-open state. | | sidebarCollapsed | boolean | Current rail-collapsed state. | | sidebarBreakpoint | "narrow" \| "medium" \| "wide" | Same breakpoint used by Layout. | | onSidebarOpenChange | (open, reason) => void | Called when the trigger changes overlay state. | | onSidebarCollapsedChange | (collapsed) => void | Called when the trigger changes rail collapse. |

import { SidebarTrigger } from "@gnome-ui/layout";

<SidebarTrigger
  sidebarOpen={sidebarOpen}
  sidebarCollapsed={sidebarCollapsed}
  onSidebarOpenChange={setSidebarOpen}
  onSidebarCollapsedChange={setSidebarCollapsed}
/>

AppHeader

Opinionated application header for Layout.header. It keeps the GNOME HeaderBar shape while exposing shell-friendly slots.

| Prop | Type | Description | |------|------|-------------| | title | ReactNode | Header title. String titles render with WindowTitle. | | subtitle | string | Optional subtitle for string titles. | | leading | ReactNode | Leading controls, usually sidebar/back buttons. | | navigation | ReactNode | Optional top-level navigation such as ViewSwitcher. | | search | ReactNode | Optional search control. | | actions | ReactNode | Trailing actions, usually flat icon buttons or menus. | | flat | boolean | Blend the header into the window chrome. |

import { AppHeader } from "@gnome-ui/layout";
import { Button, SearchBar } from "@gnome-ui/react";

<AppHeader
  title="Files"
  subtitle="Home"
  leading={<Button variant="flat" aria-label="Toggle sidebar">☰</Button>}
  search={<SearchBar inline open aria-label="Search files" />}
  actions={<Button variant="flat">New Folder</Button>}
/>

PageContent

Scrollable page body for Layout.children. It provides GNOME page padding and optional Adwaita Clamp behaviour for readable widths.

| Prop | Type | Default | Description | |------|------|---------|-------------| | as | ElementType | "main" | Element to render. Use section when nested inside another main. | | maxWidth | "none" \| "sm" \| "md" \| "lg" \| "xl" \| number | "none" | Optional content clamp. | | padding | "none" \| "compact" \| "normal" \| "spacious" | "normal" | Responsive page padding. |

import { PageContent } from "@gnome-ui/layout";

<PageContent as="section" maxWidth="lg">
  ...
</PageContent>

StatusBar

Compact footer/status bar for Layout.footer.

| Prop | Type | Description | |------|------|-------------| | children | ReactNode | Leading status content. | | trailing | ReactNode | Optional trailing status or actions. |

import { StatusBar } from "@gnome-ui/layout";

<StatusBar trailing="GNOME Files 48.0">
  1,248 items
</StatusBar>

IconBadge

Rounded-square tinted icon container. Accepts the seven gnome-ui named colors or any hex value (#rgb / #rrggbb). In both cases the background is rendered at 15% opacity.

| Prop | Type | Default | Description | |------|------|---------|-------------| | color | "blue" \| "green" \| "yellow" \| "orange" \| "red" \| "purple" \| "brown" \| string | — | Named color token or any hex value. Omit for a neutral grey background. | | size | "xs" \| "sm" \| "md" \| "lg" \| "xl" | "md" | Badge size | | children | ReactNode | — | Icon, emoji, or any inline content |

All <div> props are forwarded to the root element.

import { IconBadge } from "@gnome-ui/layout";
import { Icon } from "@gnome-ui/react";
import { GoHome } from "@gnome-ui/icons";

// Named color token
<IconBadge color="blue" size="lg">🚀</IconBadge>

// Arbitrary hex — same 15% tinted background
<IconBadge color="#6c8ebf" size="md"><Icon icon={GoHome} size="sm" /></IconBadge>
<IconBadge color="#ddd" size="sm">📄</IconBadge>

// No color — neutral grey overlay
<IconBadge size="md">📄</IconBadge>

CounterCard

Metric card with an animated numeric counter. Counts from 0 (or from the previous value) to value using an ease-out cubic curve. Respects prefers-reduced-motion.

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | — | Text label shown above the value | | value | number | — | Numeric target value | | prefix | string | — | String prepended to the number (e.g. "$") | | suffix | string | — | String appended to the number (e.g. " files") | | decimals | number | 0 | Decimal places to display | | format | (n: number) => string | — | Custom formatter; overrides decimals | | animated | boolean | true | Animate the counter on mount and value change | | duration | number | 1000 | Animation duration in ms | | accent | boolean | false | Render the value in the accent color | | interactive | boolean | false | Make the card clickable |

import { CounterCard } from "@gnome-ui/layout";

<CounterCard label="Documents" value={1248} suffix=" files" />
<CounterCard label="Revenue"   value={9420} prefix="$" accent duration={1500} />

StatCard

Key metric card with optional unit, trend indicator, icon, and skeleton loading state. Use it for dashboard metrics that need context beyond a raw count.

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | — | Metric label | | value | number \| string | — | Primary value | | unit | string | — | Optional unit suffix | | trend | { direction: "up" \| "down" \| "neutral"; value: number; period?: string } | — | Optional trend indicator | | icon | ReactNode | — | Optional visual element. Size and color are controlled by the node you pass in. | | loading | boolean | false | Render a skeleton placeholder state |

import { Person } from "@gnome-ui/icons";
import { Icon } from "@gnome-ui/react";
import { StatCard } from "@gnome-ui/layout";

<StatCard
  label="Active Users"
  value={1284}
  unit="users"
  icon={<Icon icon={Person} size="lg" />}
  trend={{ direction: "up", value: 12, period: "vs last week" }}
/>

Per-component path:

import { StatCard } from "@gnome-ui/layout/components/StatCard";

UserCard

User identity panel for popovers, sidebar footers, and profile pages. Renders an Avatar, a display name, an optional sub-line, and a list of action buttons. A separator is automatically inserted before the first "destructive" action when non-destructive actions precede it.

The component has no card chrome — place it inside a Popover or wrap it in <Card>.

| Prop | Type | Default | Description | |------|------|---------|-------------| | name | string | — | Display name; also drives avatar initials and color | | email | string | — | Optional secondary line | | avatarSrc | string | — | Avatar image URL; falls back to initials | | avatarColor | AvatarColor | — | Override the auto-derived avatar color | | avatarSize | AvatarSize | "md" | Avatar size | | actions | UserCardAction[] | [] | Action buttons; use variant: "destructive" for danger actions | | minWidth | number | 200 | Minimum card width in px |

import { UserCard } from "@gnome-ui/layout";

<UserCard
  name="Ada Lovelace"
  email="[email protected]"
  actions={[
    { label: "View Profile",     onClick: () => {} },
    { label: "Account Settings", onClick: () => {} },
    { label: "Sign Out",         onClick: () => {}, variant: "destructive" },
  ]}
/>

PanelCard

Card with a structured header / body / footer layout and built-in collapse/expand behaviour.

The expanded state is managed internally. Control it imperatively via a ref:

import { useRef } from "react";
import { PanelCard } from "@gnome-ui/layout";
import type { PanelCardHandle } from "@gnome-ui/layout";

const ref = useRef<PanelCardHandle>(null);

// Drive from anywhere in the parent:
ref.current?.expand();
ref.current?.collapse();
ref.current?.toggle();

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | ReactNode | — | Primary title shown in the header | | icon | ReactNode | — | Leading icon in the header | | headerActions | ReactNode | — | Controls at the trailing edge of the header, before the chevron | | defaultExpanded | boolean | true | Initial expanded state | | collapsible | boolean | true | Show the chevron toggle; set false to lock the panel open | | onExpandedChange | (expanded: boolean) => void | — | Notification callback — fires on every state transition | | children | ReactNode | — | Body content (collapsed/expanded with the panel) | | footer | ReactNode | — | Leading content in the footer bar (feedback text, badge…) | | footerActions | ReactNode | — | Trailing controls in the footer bar |

The footer bar is only rendered when at least one of footer or footerActions is provided.

Ref handle

| Method | Description | |--------|-------------| | expand() | Expand the panel | | collapse() | Collapse the panel | | toggle() | Toggle between expanded and collapsed |

import { Icon, Button, Text } from "@gnome-ui/react";
import { FolderOpen } from "@gnome-ui/icons";
import { PanelCard } from "@gnome-ui/layout";
import type { PanelCardHandle } from "@gnome-ui/layout";

const panelRef = useRef<PanelCardHandle>(null);

<PanelCard
  ref={panelRef}
  icon={<Icon icon={FolderOpen} />}
  title="Project Files"
  headerActions={<Button variant="flat" size="sm">Rename</Button>}
  onExpandedChange={(open) => console.log("panel:", open)}
  footer={<Text variant="caption" color="dim">Last modified: 2 min ago</Text>}
  footerActions={<Button variant="suggested" size="sm">Save</Button>}
>
  <p>Panel body content here</p>
</PanelCard>

DashboardGrid

Responsive 12-column CSS Grid container for dashboard widgets and panels.

Breakpoints

| Key | Min-width | |-----|-----------| | xs | — (base) | | sm | 576 px | | md | 768 px | | lg | 992 px | | xl | 1200 px | | xxl | 1600 px |

DashboardGrid props

| Prop | Type | Default | Description | |------|------|---------|-------------| | columns | 1–12 \| "auto" \| Partial<Record<Breakpoint, 1–12>> | "auto" | Column count. "auto" fills columns with minmax(280px, 1fr). An object maps breakpoints to explicit counts. | | gap | "sm" \| "md" \| "lg" \| Partial<Record<Breakpoint, "sm" \| "md" \| "lg">> | "md" | Gap between items — fixed or responsive. Uses --gnome-space-2/3/4. | | layout | "grid" \| "column" | "grid" | Render as a grid or as a vertical stack. |

DashboardGrid.Item props

| Prop | Type | Default | Description | |------|------|---------|-------------| | span | 1–12 \| Partial<Record<Breakpoint, 1–12>> | 1 | Column span — fixed or responsive per breakpoint. | | offset | 0–11 | 0 | Columns to skip before the item. |

Both DashboardGrid and DashboardGrid.Item forward all <div> props to their root element.

import { DashboardGrid } from "@gnome-ui/layout";

// Responsive columns + responsive span
<DashboardGrid columns={{ xs: 1, sm: 2, md: 3, lg: 4 }} gap="md">
  <DashboardGrid.Item span={{ xs: 12, md: 8 }}>
    <Chart />
  </DashboardGrid.Item>
  <DashboardGrid.Item span={{ xs: 12, md: 4 }}>
    <Sidebar />
  </DashboardGrid.Item>
</DashboardGrid>

// Fixed 12-column layout with offset
<DashboardGrid columns={12} gap="md">
  <DashboardGrid.Item span={8} offset={2}>
    <CenteredPanel />
  </DashboardGrid.Item>
</DashboardGrid>

// Vertical stack
<DashboardGrid layout="column" gap="md">
  <DashboardGrid.Item><StatCard /></DashboardGrid.Item>
  <DashboardGrid.Item><ActivityFeed /></DashboardGrid.Item>
</DashboardGrid>

MasonryGrid

Masonry layout that distributes variable-height items across columns using a shortest-column-first algorithm — each new item is placed in the column with the least accumulated height, minimising gaps. Layout is computed in JavaScript via ResizeObserver and adapts automatically when the container or any item changes size.

| Prop | Type | Default | Description | |------|------|---------|-------------| | columns | number \| Partial<Record<Breakpoint, number>> | 3 | Column count — fixed or per breakpoint. | | gap | "sm" \| "md" \| "lg" \| Partial<Record<Breakpoint, "sm" \| "md" \| "lg">> | "md" | Gap between items — fixed or responsive. | | fresh | boolean | false | Attach a ResizeObserver to every child so the layout recomputes when any item grows or shrinks. |

Uses the same breakpoints as DashboardGrid (xs / sm / md / lg / xl / xxl).

import { MasonryGrid } from "@gnome-ui/layout";

<MasonryGrid columns={{ xs: 1, sm: 2, md: 3 }} gap="md">
  <Card height={180} />
  <Card height={260} />
  <Card height={120} />
</MasonryGrid>

QuickActions

Grid of shortcut action buttons for dashboards, file managers, and control panels. Actions are keyboard navigable with arrow keys, and disabled actions are skipped and cannot be activated.

| Prop | Type | Default | Description | |------|------|---------|-------------| | actions | QuickAction[] | — | Shortcut action definitions | | columns | number | 4 | Number of grid columns |

QuickAction

| Field | Type | Description | |-------|------|-------------| | id | string | Stable action id | | label | string | Visible button label | | icon | ReactNode | Icon or visual element. Size and color are controlled by the node you pass in. | | disabled | boolean | Visually disables the action and blocks activation | | onActivate | () => void | Called when the action is clicked or activated from the keyboard |

import { Add, Save, Share } from "@gnome-ui/icons";
import { Icon } from "@gnome-ui/react";
import { QuickActions } from "@gnome-ui/layout";

<QuickActions
  columns={3}
  actions={[
    {
      id: "new-file",
      label: "New File",
      icon: <Icon icon={Add} size="lg" />,
      onActivate: () => {},
    },
    {
      id: "save",
      label: "Save",
      icon: <Icon icon={Save} size="lg" />,
      onActivate: () => {},
    },
    {
      id: "share",
      label: "Share",
      icon: <Icon icon={Share} size="lg" />,
      disabled: true,
      onActivate: () => {},
    },
  ]}
/>

ToastProvider / useToast

Transient in-app notifications shown at the bottom-center of the screen, following GNOME HIG toast guidelines. Toasts auto-dismiss after a configurable timeout and are shown one at a time (queue-based).

Wrap your app (or the relevant subtree) with <ToastProvider>, then call useToast() to show messages from any component.

| Method | Signature | Description | |--------|-----------|-------------| | show | (options: ToastOptions) => string | Enqueues a toast and returns its id | | dismiss | (id?: string) => void | Dismisses the toast with the given id (or the current one if omitted) | | dismissAll | () => void | Clears the entire queue immediately |

ToastOptions

| Field | Type | Default | Description | |-------|------|---------|-------------| | title | string | — | Toast message text | | type | "default" \| "info" \| "success" \| "warning" \| "error" | "default" | Visual variant | | timeout | number | 4000 | Auto-dismiss delay in ms. 0 = no auto-dismiss | | action | { label: string; onClick: () => void } | — | Optional inline action button | | id | string | auto | Stable id for deduplication |

import { ToastProvider, useToast } from "@gnome-ui/layout";

// Wrap your app:
<ToastProvider>
  <App />
</ToastProvider>

// Inside any component:
function SaveButton() {
  const { show } = useToast();
  return (
    <button onClick={() => show({ title: "File saved", type: "success" })}>
      Save
    </button>
  );
}

Banner

Persistent in-app message strip shown at the top of a view, following GNOME HIG banner guidelines. Use banners to communicate ongoing states — offline mode, read-only access, pending updates. They do not auto-dismiss. For individual events and short-lived messages, use Toast instead.

| Prop | Type | Default | Description | |------|------|---------|-------------| | type | "default" \| "info" \| "success" \| "warning" \| "error" | "default" | Visual variant | | action | { label: string; onClick: () => void } | — | Optional inline action button | | onDismiss | () => void | — | Shows a close button; parent controls visibility | | children | ReactNode | — | Banner message content |

import { Banner } from "@gnome-ui/layout";

<Banner
  type="warning"
  action={{ label: "Reconnect", onClick: retry }}
  onDismiss={() => setVisible(false)}
>
  Working offline — changes will sync when you reconnect
</Banner>

License

MIT