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:
| Breakpoint | Width | Usage in app |
|---|---|---|
sm | 640px | Rare — occasional flex-row reflow on small tablets |
md | 768px | Occasional text size adjustments |
lg | 1024px | Primary desktop breakpoint — sidebar nav, label reveals, icon button expansion |
xl | 1280px | Not used |
2xl | 1536px | Not 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.
| Class | Width | Where used |
|---|---|---|
max-w-sm | 384px | Onboarding panels and narrow content sections |
max-w-lg | 512px | Default dialog width (Dialog and AlertDialog base) |
max-w-2xl | 672px | Settings 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:
<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.