build69
build69  /  Components  /  Page Structure  /  LauncherGrid

LauncherGrid

v1.0.2Updated 19 Oct 2026

The tile grid a suite's start page opens on: one tile per application, with an icon, a one-line description, an optional badge and an optional featured tile that spans two columns. Columns step 1 → 2 → the requested count, so the one-column case a phone sees first is the one that is right. A tile with an href is a LINK and one with a handler is a BUTTON, because those are different things to a keyboard and to a middle click; a disabled tile is always a button, since an anchor cannot be disabled and a dead link still navigates on Enter. Carries its own empty and loading states, and loading draws skeleton TILES so the grid keeps its shape.

Preview

Live · 9 stories
DefaultOpen ↗
▸Why this is recommendedlauncherappstilesstart page+5

The default, out-of-the-box LauncherGrid.

Recommended for+14
  • ✓A start page listing the apps someone can open
  • ✓An app switcher or launcher for a suite of products
  • ✓A home screen of modules with one highlighted
  • ✓Show which apps a user does not have access to, and why
Tags+6
launcherappstilesstart pagehomesuitegridapp switcherdefault
Features+4
  • Responsive columns — 1 at the narrowest, 2 from sm, the requested 2–6 from lg; every recipe is a literal class, never lg:grid-cols-${n}, which Tailwind’s scanner cannot see
  • A featured tile spans two columns from md up and is an ordinary tile below, where spanning would only break the grid
  • Link or button per tile from the props given, so middle-click and open-in-new-tab work where they should
  • linkComponent for the host’s router — without it a Next.js app reloads the document on every tile
  • A disabled tile carries its reason in the title AND the accessible name, and renders as a disabled button rather than a dead link
  • Optional groups — labelled bands for a suite too big for one flat grid, all sharing the column behaviour so tiles stay aligned
  • Loading: skeleton tiles on the same column recipe (labelled role="status" + aria-busy), not a centred spinner that lets the page reflow
  • Empty: a real state for a tenant with no applications assigned yet, replaceable with emptyState
  • Long names truncate and descriptions clamp at two lines, so one verbose app cannot make its row taller than the others
  • role="list" / role="listitem" structure with a labelled grid
  • i18n: labels prop over DEFAULT_LAUNCHER_GRID_LABELS — the grid name, both empty-state lines, the fallback unavailable reason and the loading announcement
Not recommended for−2
  • ✕Navigation inside one app (use Sidebar or AppBar menus)
  • ✕A list of records (use List or DataTable)
  • ✕Marketing feature cards (use the sections/ features blocks)
  • ✕A dense icon-only switcher in a top bar (use the AppBar launcher button)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "LauncherGrid" })

Usage

import { LauncherGrid } from '@/components/patterns/launcher-grid';
import Link from 'next/link';

<LauncherGrid
  columns={4}
  linkComponent={Link}
  apps={[
    { id: 'invoices', name: 'Invoices', description: 'Quote, bill and chase',
      icon: <Receipt />, href: '/apps/invoices', badge: '12 due', featured: true },
    { id: 'stock', name: 'Stock', icon: <Package />, href: '/apps/stock' },
    { id: 'audit', name: 'Audit log', icon: <ClipboardList />, href: '/apps/audit',
      disabled: true, disabledReason: 'Owner access only' },
  ]}
/>

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
apps

The flat case. LauncherApp = { id, name, description?, icon?, href?, onSelect?, badge?, disabled?, disabledReason?, featured? }. Ignored when groups is given

LauncherApp[]—
groups

LauncherGroup = { id, label, apps } — labelled bands for a suite too big for one flat grid

LauncherGroup[]—
columns

Columns at the widest breakpoint (default 4). The grid steps 1 → 2 → this; a value outside the range is clamped rather than producing a grid with no columns at all

2 | 3 | 4 | 5 | 6—
linkComponent

The host's router link (next/link, react-router Link). Tiles with an href render through it; without it they are plain anchors and every click reloads the document

ElementType—
loading

Draw skeleton tiles instead of apps. Wins over apps/groups

boolean—
loadingCount

How many skeleton tiles (default 6, minimum 1)

number—
emptyState

Replaces the built-in empty state entirely, for a host that wants its own call to action

ReactNode—
labels

Override any built-in string. Keys: applications, emptyTitle, emptyHint, unavailable, loadingApplications. English defaults ship verbatim as DEFAULT_LAUNCHER_GRID_LABELS. A tile’s own disabledReason is a prop on the app, not a label, so each one can say something different

Partial<LauncherGridLabels>—

Related