agentsclimarketplace

Bms frontend design

Skill bmsuisse/skills/skills/bms-frontend-design

The BMS visual identity for internal web apps — left navigation layout, BMS red accent color, background/surface scale, border-radius scale, a minimal zebra-striping pattern for data grids, the Heroicons `24/outline` icon set with a curated concept-to-icon mapping, and the responsive/mobile breakpoint where the sidebar becomes a slide-in drawer. Use this whenever scaffolding a new internal BMS app's UI, building or restyling a sidebar/nav, picking colors for a new app, styling a data table or grid, choosing icons, making a layout responsive/mobile-friendly, or reviewing frontend code for visual consistency with other BMS apps. Trigger on requests like "make this look like our other internal tools", "add a sidebar nav", "what colors should I use", "style this table", "what icon should I use for X", "how do I make this responsive", or "set up the theme" for a BMS app — even if the user doesn't say "design system" explicitly. Supersedes generic frontend-aesthetic advice (e.g. "pick a bold, unique look per app") for anything under the BMS brand — the point here is consistency across apps, not differentiation.From its SKILL.md

Install
npx -y skills add bmsuisse/skills --skill bms-frontend-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

11.8 KB, as published. Nobody here has run it

BMS Frontend Design

Concrete, reusable visual conventions for BMS internal web apps, extracted from two production codebases (OneSales, CCMT2). Unlike generic "make it look distinctive" advice, the goal here is the opposite: every internal BMS app should feel like it belongs to the same family. Apply these values directly — don't reinvent a new palette or radius scale per app.

This assumes a Tailwind v4 + shadcn/ui setup (CSS variables consumed via @theme inline, bg-background/text-foreground-style utilities, a .dark class variant) — the same setup init-app-stack scaffolds. Define the variables below in src/index.css alongside the generated shadcn tokens rather than hardcoding hex values in components; that's what makes dark mode and future rebrands (a different --primary) work for free.

Layout — left navigation

A resizable sidebar on the left is the default shell for any internal BMS app with more than a couple of pages.

  • Default width 240px, resizable between 180–420px, persisted (e.g. localStorage). Collapses to a 56px icon-only rail rather than hiding entirely — users should always have nav access.
  • Sidebar background: near-white, --sidebar: #fcfcfc. Border on the right edge only, using the app's subtle border token (--border: #f2f2f2 light). Don't add a border-radius or shadow to the sidebar itself — it's a flat plane, not a card.
  • Nav rows use rounded-lg (8px) corners and an 18×18px icon from @heroicons/react (24/outline) — see references/icons.md for the concept-to-component mapping and why not to hand-draw icons.
  • Inactive row: text at 55% opacity of the foreground color, icon in the muted-foreground color, icon stroke-width 1.75. This keeps the resting state quiet so the active item reads clearly.
  • Hover (inactive rows): muted background at 60% opacity — a hint, not a highlight.
  • Active row: full-opacity text + font-semibold, background = the brand accent at 8% tint (bg-nav-primary/8), icon colored with the brand accent, stroke-width 2. The 8% tint is deliberately subtle — this is not a filled pill, just enough wash to draw the eye.
  • Section group labels (e.g. "Work", "Tools", "Info"): 11px, medium weight, muted-foreground at 80% opacity, with a collapse chevron per group.
--sidebar: #fcfcfc;
--nav-primary: #DC001A; /* BMS red — see Brand colors below */
// active nav item
className="rounded-lg font-semibold text-foreground bg-nav-primary/8"
// icon: className="h-[18px] w-[18px] text-nav-primary" strokeWidth={2}

// inactive nav item
className="rounded-lg text-foreground/55 hover:bg-muted/60 hover:text-foreground"
// icon: className="h-[18px] w-[18px] text-muted-foreground" strokeWidth={1.75}

Responsive — mobile nav

Tailwind's responsive system is mobile-first: an unprefixed utility applies at every size, and a breakpoint prefix (sm:, md:, lg:, xl:, 2xl:) overrides it starting at that min-width. Default to writing the mobile layout unprefixed and layering md: (768px) on top for desktop, rather than the other way round — it's easy to end up with desktop-only CSS by accident if you start from a lg: design and never account for what happens below it.

The persistent 240px sidebar (above) is a desktop pattern, not a mobile one. Below md, replace it entirely with a slide-in drawer rather than trying to shrink it further:

  • Sidebar <aside>: hidden md:relative md:flex — invisible below md, the normal resizable sidebar at md and above.
  • Below md: a Sheet/drawer component sliding in from the left (side="left"), width min(352px, 88vw) so it caps out on tablets but still fits narrow phones, opened by a menu button that itself is only shown below md (md:hidden on the trigger).
  • Drag-to-resize and the collapse-to-rail toggle are desktop-only affordances (md:block / md:inline-flex on those controls) — mobile nav is just open-or-closed, not resizable or collapsible to a rail.
  • Respect safe areas on mobile: pad with env(safe-area-inset-*) (commonly aliased to CSS vars like --sat/--sab/--sal/--sar) so content doesn't sit under a notch or a home indicator.
  • Use 100dvh (dynamic viewport height), not 100vh, for full-height mobile layouts — 100vh doesn't account for the browser chrome that appears/disappears as the user scrolls on mobile, which leaves a gap or causes scroll-jank.

Brand colors

BMS red is the signature accent: #DC001A (light) / oklch(0.58 0.2 27) (dark). There are two legitimate ways to use it — pick one per app rather than mixing them:

  1. Red as accent only — keep the app's --primary a neutral navy (#2e4a62 light / #6B90B8 dark) for buttons and links, and reserve red specifically for --nav-primary (the sidebar active-state highlight). This is the more common pattern — it keeps red as a "you are here" signal rather than spraying it across every button.
  2. Red as primary — set --primary (and --ring) to BMS red directly, for an app that wants to lead with the brand color everywhere (buttons, links, focus rings), not just in the nav.

Either way, define it as a CSS variable (--nav-primary and/or --primary), never a hardcoded hex in a component — that's what lets dark mode and future brand variants work without a find-and-replace.

Semantic colors follow the same hue-preserving pattern between light and dark mode: keep the hue, raise lightness and saturation slightly for dark backgrounds so they still read as the same color family.

MeaningLightDark (same hue, lighter)
Destructive / erroroklch(0.56 0.22 27)oklch(0.68 0.21 27)
Positive / successoklch(0.62 0.14 150)oklch(0.76 0.15 165)
Warningoklch(0.72 0.14 70)oklch(0.82 0.14 80)
Infooklch(0.60 0.12 230)oklch(0.76 0.13 245)

Background & surface scale

Keep it to three tiers — page, card, muted — rather than the overlapping --surface/--surface-2/--surface-3 families that tend to accumulate once a codebase has been through a few redesigns. Three tiers is enough to express elevation and easier for every contributor to reason about.

TierUse forLightDark
--backgroundPage/app shell#fcfcfc (near-white)oklch(0.165 0.022 265)
--cardCards, panels, modalsoklch(1 0 0) (pure white)oklch(0.205 0.026 265)
--mutedChips, table headers, subtle fillsoklch(0.975 0.002 250)oklch(0.245 0.030 265)

Note that cards are whiter than the page background in light mode — that subtle lift is what makes a card read as elevated without needing a shadow.

Border-radius scale

Use one consistent scale everywhere instead of picking a radius per component. Source material had three competing --radius definitions in the same file — avoid repeating that:

RadiusUse for
6pxCompact/xs controls
8pxDefault buttons, inputs, nav rows — the default
12pxCards, kanban-style cards, modals
16–20pxHero/feature cards, prominent panels
full (9999px)Badges, pills, avatars, segmented controls

Typography

  • Inter (variable font), falling back to system-ui. Base body size ~15px, letter-spacing -0.01em, antialiased.
  • Amounts, KPIs, and other numeric displays: tabular numerals (font-variant-numeric: tabular-nums) so digits align in columns.
  • Number formatting: Swiss convention, not US. Format with the de-CH locale (value.toLocaleString('de-CH', { minimumFractionDigits: 2, maximumFractionDigits: 2 })) rather than hardcoding a comma separator — it renders the thousands separator as an apostrophe (128'450.00), which is what a comma would silently get wrong for a Swiss audience.
  • Small "eyebrow" labels (section headers, table column meta): ~10–11px, bold/semibold, wide letter-spacing (~0.1–0.12em), often uppercase.

Data grids — minimal zebra

A very subtle alternating-row treatment, not a classic high-contrast zebra table.

  • Stripe alternating rows with the theme's foreground/text color at 5% opacity (e.g. Tailwind's bg-foreground/5) — never a hardcoded gray hex. Deriving the stripe from the foreground token means it automatically adapts to dark mode without a separate dark-mode override.
  • If the grid is virtualized (e.g. TanStack Virtual, only visible rows exist in the DOM), do not stripe with CSS :nth-child/even: selectors — that stripes by DOM position, which visibly flickers as rows mount/unmount during scroll. Key the stripe off the logical row index instead (row.index % 2 === 1), not DOM position.
  • Row hover: the theme's accent color at 50% opacity (hover:bg-accent/50), layered on top of the stripe.
  • Wrap the whole table in a single rounded-md (~10px) bordered container — no per-cell borders.
  • Row separators: a single bottom border per row (the theme's subtle border token) — no vertical/left/right borders.
  • Header row: slightly muted background (bg-muted), often sticky to the top of the scroll container.
  • Cell padding: compact, ~8px (p-2), small text size.
  • Numeric columns (amounts, counts, percentages): right-align both the header and the cells, and use tabular numerals — right-aligned digits are what let a reader compare magnitudes down a column at a glance; left- or center-aligned numbers don't line up on their most significant digit.
className={cn(
  "border-b p-2 text-sm",
  isClickable && "cursor-pointer hover:bg-accent/50",
  rowIndex % 2 === 1 && "bg-foreground/5",
)}

Quick reference

  • Sidebar: 240px default, 180–420px resizable range, 56px collapsed rail
  • Mobile (<md): sidebar replaced by a slide-in drawer, not shrunk in place; safe-area padding and 100dvh used instead of 100vh
  • Nav active state: bg-nav-primary/8 + full-opacity text, not a filled pill
  • Brand red #DC001A used as accent-only or full-primary — pick one, don't mix
  • Backgrounds: 3-tier scale (--background → --card → --muted), not more
  • Border-radius: 6/8/12/16-20/full — no ad hoc values
  • Font: Inter, ~15px base, tabular numerals for numbers
  • Numbers formatted de-CH (apostrophe thousands separator), not a hardcoded comma
  • Grid zebra: bg-foreground/5 on alternating rows, keyed off logical index if virtualized
  • Numeric grid columns: right-aligned header + cells, tabular numerals
  • Icons: @heroicons/react (24/outline) only, per references/icons.md — never hand-drawn, and never lucide-react (that's shadcn-internal only)

Keep looking

Skills are one crate of 626,194. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.