@david-richard/notify-ds
v1.2.0
Published
Qu Notify design system — tokens, components, and brand assets for the Qu Notify restaurant analytics app.
Downloads
1,995
Maintainers
Readme
notify-ds
Design system for Qu Notify — the restaurant analytics mobile app by Qu. Contains tokens, components, fonts, icons, screenshots, and brand assets.
For AI design agents: Read constraints.md first, then screen-anatomy.md, then come back here for the API surface. Those two files are load-bearing.
What's in this repo
notify-ds/
├── tokens.json ← W3C DTCG format. Single source of truth for all values.
├── tokens.css ← CSS custom properties. Import this in any HTML/CSS project.
├── tailwind.config.js ← Tailwind v3 preset. Use as a preset in consuming projects.
├── constraints.md ← Hard never/always rules. Read before building any UI.
├── screen-anatomy.md ← Every screen decomposed spatially. Read before building any screen.
├── CHANGELOG.md ← Decision log — why things are the way they are.
├── components/ ← React/TypeScript components
│ ├── index.ts ← Barrel export (single import surface)
│ ├── button.tsx
│ ├── input.tsx
│ ├── checkbox.tsx
│ ├── radio.tsx
│ ├── toggle.tsx
│ ├── selector.tsx
│ ├── tabs.tsx
│ ├── switcher.tsx
│ ├── bottom-nav.tsx
│ ├── badge.tsx
│ └── metric-tile.tsx
├── assets/
│ ├── logo-notify-lockup.svg
│ ├── logo-qu.svg
│ ├── logo-q-mark.svg
│ └── icons/ ← SVG stroke icons, one file per icon
├── fonts/ ← Zilla Slab TTF (all weights). Inter + Red Hat Text via Google Fonts.
└── screenshots/ ← 27 production app screenshots, named descriptivelyProduct context
Qu Notify is an iOS-first mobile app used by restaurant franchisees, group managers, and store managers to monitor real-time sales, labor, and kitchen performance. It is checked multiple times daily — it is a utility, not an experience. Clarity and data density take priority over decoration.
Primary screens: Splash → Sign In → Dashboard (Sales/Labor/Store/Product tabs) → Metric detail → Menu overlay → Check Search → Tills → Kitchen Intelligence
Users: Franchisees, group managers, store managers
Data shown: Net Sales, Checks, Average Check, Payments, Gross Sales, Discounts, Cash, Tills (Open/Closed/Reconciled), Voids, Service Charges, Labor, Speed of Service, Kitchen fulfillment times
Quick start
As a Tailwind preset
// tailwind.config.js
const notifyPreset = require('notify-ds/tailwind')
module.exports = {
presets: [notifyPreset],
content: ['./src/**/*.{ts,tsx,html}'],
}CSS custom properties
/* In your global CSS */
@import 'notify-ds/tokens.css';React components
import { Button, MetricTile, TabBar, InputField } from 'notify-ds/components'
// Dashboard tile
<MetricTile label="Net Sales" value="$345.58" trend={11.8} trendLabel="vs yesterday" />
// Primary CTA
<Button variant="primary" size="lg">Sign In</Button>
// Tab bar
<TabBar tabs={["Sales", "Labor", "Store", "Product"]} value={tab} onValueChange={setTab} />Tokens (raw JSON)
const tokens = require('notify-ds/tokens')
const techBlue = tokens.primitive.color.brand['tech-blue'].$value // "#40CCF2"Inline SVG icons
<!-- Direct reference -->
<img src="node_modules/notify-ds/assets/icons/search.svg" />
<!-- Or import and inline (recommended) -->
import SearchIcon from 'notify-ds/assets/icons/search.svg'Design fundamentals
Colors (key values)
| Token | Value | Usage |
|---|---|---|
| primitive.color.brand.tech-blue | #40CCF2 | Primary CTA buttons, input focus border, context selectors, checkbox on-state, toggle on-state |
| primitive.color.brand.teal | #339FB8 | Secondary button outline/text, link text, action labels, check numbers |
| primitive.color.neutral.black | #000000 | Primary text, tab/nav selected fill |
| primitive.color.neutral.white | #FFFFFF | Card backgrounds, text on dark fills |
| primitive.color.neutral.gray-50 | #F4F4F4 | App screen background (never white at page level) |
| primitive.color.neutral.gray-100 | #DEDEDE | Tab bar container, switcher container, inactive button bg |
| primitive.color.brand.red | #EF2149 | Error borders, error text, required asterisk, toast bg |
Critical: #40CCF2 (cyan) is the CTA button color. #000000 (black) is the selected tab/nav color. These are separate roles and must not be swapped.
Typography
| Family | Role | Usage | |---|---|---| | Inter | Primary | Body text, data labels, metric values, captions | | Red Hat Text | Secondary | Page headers, input labels, button labels, section titles | | Zilla Slab | Display | Splash screen, H2 display headings, pull quotes only |
Font loading is the consumer's responsibility. tokens.css declares font families (--font-primary, --font-display, etc.) but does not load any font files — the canonical mechanism is a single HTML <link> to Google Fonts covering all four families:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Red+Hat+Text:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Red+Hat+Display:wght@400;500;600;700;800;900&family=Zilla+Slab:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap"
>For meta-frameworks (TanStack Start, Next, Remix, etc.) put the equivalent <link> in the root route's head(). Don't try to @import url('https://fonts.googleapis.com/...') from your CSS — Tailwind v4 / Vite inline tokens.css and strip the @import statement silently, so fonts won't load.
Zilla Slab TTF files ship at dist/fonts/ for offline / self-hosting scenarios, but tokens.css does not reference them by default. If you need to self-host, add your own @font-face declarations in your project CSS pointing at copies in your public/ directory.
Shapes
- Inputs: Always
border-radius: 9999px(full pill). No exceptions. - Buttons: Always
border-radius: 9999px(full pill). No exceptions. - Metric tiles:
border-radius: 16px - Modals:
border-radius: 24px - Bottom nav:
border-radius: 60px - Tab bar container:
border-radius: 9999px - Switcher container:
border-radius: 8px(not pill — this distinguishes it from TabBar)
Disabled states
Always opacity: 0.5 on the entire component. Never a color swap. The component's current fill/stroke colors remain intact underneath the opacity reduction.
Dark mode
Two modes ship: Light (default) and Dark. Dark is opt-in — set data-theme="dark" (or class="dark") on a root element (<html> or any wrapper):
<html data-theme="dark"> … </html>Only the semantic color aliases that change are redeclared under the dark selector in tokens.css (surfaces invert to near-black, text to white, nav-selected flips, etc.). Primitives, spacing, radius, typography, and the brand/accent/status colors are mode-stable. Components consume semantic tokens, so they re-theme automatically with no per-component work. The dark values are ported 1:1 from the Figma "Semantic" collection (Dark mode) and documented in tokens.json under $extensions.qu.modes.dark. Tailwind's dark: variant is wired to both the .dark class and [data-theme="dark"].
Component API
Button
<Button
variant="primary" // primary | secondary | tertiary | link
size="md" // xsm | sm | md | lg
state="active" // active | inactive
iconOnly={false}
disabled={false}
>
Sign In
</Button>
<IconButton variant="primary" size="md"><SearchIcon /></IconButton>InputField
<InputField
type="default" // default | password | search
label="Username"
required
placeholder="[email protected]"
state="normal" // normal | active | filled | error | disabled | readonly
errorMessage="Invalid email"
helperText="We'll never share your email"
/>Checkbox / Radio / Toggle
<Checkbox label="Remember me" defaultChecked />
<Checkbox label="Select all" indeterminate />
<RadioGroup name="period" value={val} onChange={setVal} label="Time period">
<Radio value="day" label="Day" />
<Radio value="week" label="Week" />
<Radio value="month" label="Month" />
</RadioGroup>
<Toggle label="Enable notifications" checked={on} onChange={setOn} />Selector
<Selector label="All Stores" variant="primary" state="active" open={isOpen} />
<Selector label="This Week" variant="secondary" state="active" />
<SelectorGroup>
<Selector label="StoreName" icon={<StoreIcon />} />
<Selector label="01/06/26" icon={<CalendarIcon />} />
</SelectorGroup>TabBar
<TabBar
tabs={["Sales", "Labor", "Store", "Product"]}
value={activeTab}
onValueChange={setActiveTab}
/>
// With content panels:
<TabPanels value={activeTab}>
<TabPanel value="Sales"><SalesView /></TabPanel>
<TabPanel value="Labor"><LaborView /></TabPanel>
</TabPanels>Switcher
<Switcher
segments={["Day", "Week", "Month"]}
defaultValue="Week"
onValueChange={setPeriod}
/>
// With icon segments:
<Switcher segments={[{value:"list", label:"List", icon:<ListIcon/>}, ...]} />BottomNav
<BottomNavContainer>
<BottomNav
items={[
{ value: "dashboard", label: "Dashboard", icon: <DashboardIcon /> },
{ value: "inventory", label: "Inventory", icon: <BoxIcon />, badge: 3 },
{ value: "menu", label: "Menu", icon: <MenuIcon /> },
]}
value={activeRoute}
onValueChange={(v) => router.push(v)}
/>
</BottomNavContainer>BottomNavContainer positions the nav as fixed bottom-6 left-1/2 -translate-x-1/2.
MetricTile
<MetricTile
label="Net Sales"
value="$345.58"
trend={11.8}
trendLabel="vs yesterday"
/>
<MetricTileGrid cols={2}>
<MetricTile label="Net Sales" value="$345.58" trend={11.8} />
<MetricTile label="Checks" value="11" trend={18.1} />
<MetricTile label="Avg Check" value="$33.86" trend={7.7} />
<MetricTile label="Gross Sales" value="$368.40" trend={11.4} />
</MetricTileGrid>
// Loading state:
<MetricTile label="Net Sales" value="" trend={0} loading />Badge / TrendBadge
<Badge variant="success">Open</Badge>
<Badge variant="error">Closed</Badge>
<Badge variant="brand">NEW</Badge>
// Auto-picks color from sign:
<TrendBadge value={11.8} /> // → green "+11.8%"
<TrendBadge value={-5.6} /> // → red "−5.6%"Screenshots
27 production screenshots in screenshots/. Key ones for design reference:
| File | Shows |
|---|---|
| sales-dashboard.png | Full dashboard, metric tile grid, context bar, tab bar, bottom nav |
| sign-in.png | Auth screen, input field states, primary button, "Powered by" footer |
| menu-overlay.png | Bottom sheet, dark scrim, menu structure, "NEW" badge |
| net-sales.png | Detail screen, line chart, selected table row, back navigation |
| check-search.png | Check cards, teal check numbers, section headers |
| dashboard-store-kitchen.png | Nested tab bars, data table, teal action link |
| open-tills.png | Empty state pattern, 3-tab bar |
| enable-face-id.png | Modal overlay pattern |
| loading.png | Splash screen, brand illustration treatment |
| dashboard-loading-error-with-toast.png | Error state, toast pattern |
Voice & copy
- Direct and factual. No marketing copy in UI. "Net Sales" not "Your earnings today."
- Metric-first. Lead with the number; context follows.
- Sentence case for all UI copy. ALL CAPS only for
NOTIFYwordmark and section category labels (KITCHEN,NETWORK). - No emoji. Ever.
- Present tense for states: "Sign in to continue", "No Tills Found", "Something Went Wrong".
- Button labels are definitive: "Sign In", "Refresh", "Enable Face ID" — no ellipsis, no "ing" forms.
- Error toast copy (production): "Ooops, we are having problems"
Known gaps (v1.0.0)
- Toast component (anatomy in
screen-anatomy.md) - Data table component (anatomy in
screen-anatomy.md) - Kitchen Intelligence screen components (dark surface, 72px score, day-part switcher)
- Chart components (recommend Recharts with cyan/gray color scheme)
