Skip to content

Layout

Player 1 Inventory is a mobile-first application. The default (unstyled) layout targets small screens; responsive modifiers (lg:) layer on desktop enhancements. No custom layout tokens are defined — all layout is done through Tailwind’s default utilities.

Mobile-first approach

The app is used primarily on phones while shopping or cooking. The baseline layout is optimized for a single-column vertical flow on a small screen. Desktop (lg:) breakpoint additions are primarily cosmetic — revealing labels next to icons, adjusting button widths — rather than restructuring the layout.

// Desktop breakpoints reveal text that is hidden on mobile
<Button size="icon" className="lg:w-auto lg:px-3">
<Plus />
<span className="hidden lg:inline">Add</span>
</Button>
// Navigation adapts from bottom bar (mobile) to sidebar (desktop)
className="lg:flex lg:hidden"

Breakpoints

Tailwind’s default breakpoints are available. Only two are actively used in the codebase:

BreakpointWidthUsage in app
sm640pxRare — occasional flex-row reflow on small tablets
md768pxOccasional text size adjustments
lg1024pxPrimary desktop breakpoint — sidebar nav, label reveals, icon button expansion
xl1280pxNot used
2xl1536pxNot used

The lg: breakpoint is the single practical threshold between the mobile and desktop experiences.

Flex vs grid

Both flex and grid are used, but for different purposes:

Flexbox — for rows and linear arrangements

Flex is the default layout for most UI rows: toolbars, list items, badge rows, button groups. It handles horizontal alignment and flex-1 expansion naturally.

// Toolbar row
<div className="flex items-center gap-2">
<span className="flex-1">{title}</span>
<Button />
</div>
// Badge/tag row (wrapping)
<div className="flex flex-wrap gap-1.5">
{tags.map(tag => <Badge key={tag.id} />)}
</div>

Grid — for structured two-dimensional layouts

Grid is used for page-level slot layouts (e.g. grid-rows-[auto_1fr] for a fixed toolbar + scrollable content area) and for multi-column settings forms.

// Page layout: toolbar row + scrollable body
<div className="h-[100cqh] grid grid-rows-[auto_1fr]">
<div>{/* Toolbar */}</div>
<div className="overflow-y-auto">{/* Scrollable list */}</div>
</div>
// Settings form
<div className="grid grid-cols-2 gap-4">
{/* … */}
</div>

Max-width and containers

The app uses max-w-* constraints in dialogs, forms, and settings panels — not on the main list views, which stretch to fill the available width.

ClassWidthWhere used
max-w-sm384pxOnboarding panels and narrow content sections
max-w-lg512pxDefault dialog width (Dialog and AlertDialog base)
max-w-2xl672pxSettings forms and detail pages

Global shell

The global Layout component wraps every page. It uses a two-column grid that adapts at the lg: breakpoint:

Layout.tsx
<div className="h-dvh bg-background-base grid grid-cols-[auto_1fr]">
<div className="overflow-y-auto">
<Sidebar /> {/* hidden by default; lg:flex */}
</div>
<div className="grid grid-rows-[1fr_auto]">
<main className="overflow-y-auto [container-type:size]">
{children}
</main>
<Navigation /> {/* bottom bar; lg:hidden */}
</div>
</div>

Mobile (< 1024px): the sidebar column collapses (hidden), giving the main content full width. A four-tab bottom navigation bar occupies the auto row below.

Desktop (≥ 1024px): a w-56 sidebar fills the auto column on the left. The bottom bar is hidden, so the main content fills the full viewport height.

Mobile Desktop
┌──────────────────────┐ ┌────────┬───────────────────┐
│ │ │ │ │
│ Main content │ │Sidebar │ Main content │
│ (full width) │ │ w-56 │ (remaining) │
│ │ │ │ │
├──────────────────────┤ └────────┴───────────────────┘
│ Bottom nav (4 tabs) │
└──────────────────────┘

Page layout patterns

Three patterns appear inside the main content area. All three work the same at both breakpoints — only the outer shell changes.

Toolbar + scrollable list

The most common pattern. Used by the main list views (pantry, shopping, cooking) and all settings list pages.

┌─────────────────────┐
│ Fixed toolbar row │ auto height
├─────────────────────┤
│ │
│ Scrollable list │ 1fr (fills remaining height)
│ │
└─────────────────────┘
<div className="h-[100cqh] grid grid-rows-[auto_1fr]">
<div>{/* Toolbar */}</div>
<div className="overflow-y-auto [container-type:size]">
{/* Scrollable content */}
</div>
</div>

h-[100cqh] sizes the page to the container query height of the Layout’s <main> — which equals the viewport minus the bottom nav on mobile, and the full viewport height on desktop.

Fullscreen toolbar + scrollable list

Used by detail pages that hide the sidebar and bottom nav entirely (item detail, shelf detail).

<div className="h-screen grid grid-rows-[auto_1fr]">
<div>{/* Toolbar */}</div>
<div className="overflow-y-auto [container-type:size]">
{/* Scrollable content */}
</div>
</div>

Uses h-screen rather than h-[100cqh] because these pages opt out of the shell entirely — there is no container query parent to measure against.

Padded form page

Used by detail form pages (item info, tag/vendor/recipe/shelf detail forms). No toolbar slot — padded content that fills at least the container height.

<div className="p-4 bg-background-elevated min-h-[100cqh]">
{/* Form content */}
</div>

Rules of thumb

  • Design for mobile first — the default layout should work on a 375px screen.
  • Use lg: to add desktop enhancements, not to fix mobile layouts.
  • Use flex for linear rows and wrapping sets; use grid for two-dimensional page slots.
  • Apply max-w-* constraints to dialogs and forms, not to full-page lists.
  • Do not use custom pixel values for breakpoints — snap to sm/md/lg.