🍕 Roman's Pizza Design System
Design tokens, Lucide icons, and component classes — one source of truth for all RP digital products.
Gradients
All gradients follow a single rule: lightest colour at the top, darkest at the bottom. This creates natural visual hierarchy — the eye tracks downward through increasing weight — and anchors heavier UI elements (tables, footers, chart axes) with a darker, more grounded base.
Gradient tokens
--g-blue
Buttons, hero cards, badges, CTAs
--g-sidebar
Sidebar nav, dark panels
Surface gradient
Inline use — subtle depth on cards
Direction rule
Correct — light top, dark bottom
Incorrect — dark top, light bottom
Usage in CSS
/* Use the token — never repeat the raw gradient */
.my-hero { background: var(--gradient-brand-blue); }
.my-panel { background: var(--gradient-sidebar); }
/* Custom one-off: always light → dark, top → bottom */
.my-banner {
background: linear-gradient(180deg, #0260c9 0%, #014085 100%);
}Colours
Brand
#014085
#012d5e
#0260c9
#ed1c24
#c41820
Semantic Status
Rating Scale
Typography
All typography values are defined as CSS custom properties in src/tokens/typography.css. Components reference these tokens — never use raw pixel or rem values.
Font Size Scale
Font Weights
| Token | Value | Preview |
|---|---|---|
| --font-weight-regular | 400 | Regular weight text |
| --font-weight-medium | 500 | Medium weight text |
| --font-weight-semibold | 600 | Semibold weight text |
| --font-weight-bold | 700 | Bold weight text |
| --font-weight-extrabold | 800 | Extrabold weight text |
Line Heights & Letter Spacing
| Token | Value | Use |
|---|---|---|
| --line-height-tight | 1.15 | Stat values, large headings |
| --line-height-snug | 1.2 | Buttons, compact text |
| --line-height-normal | 1.4 | General content |
| --line-height-base | 1.5 | Body text default |
| --line-height-relaxed | 1.6 | Long-form text, descriptions |
| --letter-spacing-tight | -0.02em | Large headings |
| --letter-spacing-normal | 0.01em | Body text |
| --letter-spacing-wide | 0.04em | Buttons, badges |
| --letter-spacing-wider | 0.07em | Labels, uppercase text |
| --letter-spacing-widest | 0.075em | All-caps headings |
Spacing & Radii
Spacing Scale
Semantic Spacing
| Token | Value | Use |
|---|---|---|
| --spacing-page | 2rem | Outer page padding |
| --spacing-section | 2rem | Gap between major sections |
| --spacing-card | 1.375rem | Internal card padding |
Radius Tokens
Shadows
Shadow Tokens
| Token | Light Mode | Use |
|---|---|---|
| --shadow-xs | 0 1px 2px 5% black | Subtle depth, swatches |
| --shadow-sm | Dual layer, 6%+4% | Default elevation |
| --shadow-md | Dual layer, 5%+4% | Raised elements |
| --shadow-lg | Dual layer, 5%+3% | Prominent elements |
| --shadow-card | Dual layer, 7%+5% | Card default |
| --shadow-card-hover | Dual layer, blue-tinted | Card hover state |
| --shadow-pop | Deep dual layer | Modals, overlays |
| --shadow-glow | Blue brand glow | Focus rings, emphasis |
Motion
| Token | Value |
|---|---|
| --transition-fast | 150ms ease |
| --transition-base | 250ms ease |
| --ease-settle | cubic-bezier(0.25, 0.46, 0.45, 0.94) |
| --ease-spring | cubic-bezier(0.175, 0.885, 0.32, 1.275) |
Breakpoints
The design system uses three breakpoints. Desktop styles are the default — overrides are applied at narrower viewports via max-width queries. This ensures existing desktop appearance is never affected.
Breakpoint Architecture
| Name | Query | Range | Min Supported |
|---|---|---|---|
| Mobile | max-width: 767px | 0–767px | 375px |
| Tablet | min-width: 768px AND max-width: 1023px | 768–1023px | 768px |
| Desktop | min-width: 1024px | 1024px+ | 1024px |
Combined Queries
| Alias | Query | Covers |
|---|---|---|
| ≤ Tablet | max-width: 1023px | Mobile + Tablet |
| ≥ Tablet | min-width: 768px | Tablet + Desktop |
Usage in CSS
/* Mobile only */
@media (width <= 767px) {
.my-component { padding: 1rem; }
}
/* Tablet only */
@media (768px <= width <= 1023px) {
.my-component { padding: 1.5rem; }
}
/* Mobile + Tablet (≤ 1023px) */
@media (width <= 1023px) {
.my-grid { grid-template-columns: 1fr; }
}Buttons
Badges
Cards
Stat Grid
Total Reviews
1,248
+12% vs last month
Avg Rating
4.3
out of 5
Promoters
68%
Semantic Tints
Success
Warning
Error
Info
Accent Left-Border
Success
Warning
Error
Chips
Bar Charts
Chart Cards
Chart card CSS components with live Chart.js examples. The grid switches from 2-col to 1-col on mobile/tablet. See docs/chart-integration.md for full configuration guide.
Revenue Over Time
+12%Store Comparison
Rating Trends — All Branches
Chart Card Classes
<div class="rp-chart-grid">
<div class="rp-chart-card">
<div class="rp-chart-header">
<h3 class="rp-chart-title">Revenue</h3>
</div>
<div class="rp-chart-canvas-wrap">
<canvas id="myChart" role="img"
aria-label="Line chart showing revenue"></canvas>
</div>
</div>
<!-- .rp-chart-card--full spans both columns -->
</div>Tables
| Branch | Reviews | Avg Rating | Status |
|---|---|---|---|
| Sandton City | 312 | 4.6 | Active |
| Rosebank | 198 | 4.1 | Pending |
| Midrand | 87 | — | Archived |
Inputs
Modals
Rate Your Order
This is the modal card component. Backdrop fade and slide-up animate on mount.
Empty States
No reviews yet
Once customers leave reviews they'll appear here. Share your link to get started.
Loading States
Utilities
Utility classes from src/components/utilities.css and src/components/responsive.css. These work as single-purpose helpers on any element.
Text Truncation — .rp-truncate
This is a very long text string that will be truncated with an ellipsis because the container is only 250px wide.
Container constrained to 250px — text truncated with ellipsis.
Tabular Numbers — .rp-tabular-nums
Without
1,111
8,888
4,204
With .rp-tabular-nums
1,111
8,888
4,204
Tabular nums align digits in columns — critical for stat cards, tables, and numeric lists.
Horizontal Scroll — .rp-scroll-x
Horizontally scrollable container with touch momentum and thin scrollbar.
Responsive Visibility
| Class | Mobile | Tablet+ |
|---|---|---|
| .rp-hide-mobile | Hidden | Visible |
| .rp-show-mobile | Visible | Hidden |
| .rp-stack-mobile | Stacks flex children vertically | Normal flex direction |
Screen Reader Only — .sr-only
The .sr-only class visually hides an element while keeping it accessible to screen readers. Use for skip links, form labels, or additional context.
Icons
Why Lucide SVG icons exclusively
aria-label or are hidden with aria-hidden="true" when decorative, giving full control over the announcement.
currentColor, meaning icons respond to semantic colour tokens (--color-success, --color-error, etc.) and work correctly in dark mode without any extra markup.
width/height attributes map directly to the design grid. Emoji size is controlled by font-size and behaves inconsistently inside flex and grid layouts.
Click any icon to copy its usage snippet. Install: npm i lucide or see How to Use.
Loading icons…
Dark Mode
Add data-theme="dark" to <html> (or any ancestor) to switch the full token set to dark values. No extra stylesheet needed — all tokens are overridden via CSS custom properties.
Theme Toggle Component
<!-- Theme toggle component --> <button class="rp-theme-toggle" aria-label="Toggle dark mode"> <svg class="rp-theme-toggle__icon--light" data-lucide="moon" width="16" height="16"></svg> <svg class="rp-theme-toggle__icon--dark" data-lucide="sun" width="16" height="16"></svg> </button>
Implementation
/* 1. Include theme-init.js in <head> (prevents FOUWT) */
<script src="theme-init.js"></script>
/* 2. Wire the toggle button */
const btn = document.querySelector('.rp-theme-toggle');
btn.addEventListener('click', () => {
const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = next;
localStorage.setItem('rp-theme', next);
});
/* 3. CSS fallback (no JS): prefers-color-scheme in theme-fallback.css */
/* OS theme changes are auto-detected via matchMedia listener */Light vs Dark preview
Light (default)
Total Reviews
1,248
Dark
Total Reviews
1,248
Token changes in dark mode
| Token | Light | Dark | Role |
|---|---|---|---|
| --color-background | #f0f4f8 | #0a0f1a | Page background |
| --color-surface | #ffffff | #111827 | Card / panel background |
| --color-surface-dim | #f8f9fa | #1a2233 | Subtle surface / table header |
| --color-surface-container | #f3f4f6 | #1e293b | Secondary control background |
| --color-on-surface | #0f172a | #f1f5f9 | Primary text |
| --color-on-surface-muted | #64748b | #94a3b8 | Secondary / placeholder text |
| --color-outline | #cbd5e1 | #334155 | Border — inputs |
| --color-outline-variant | #e2e8f0 | #1e293b | Border — subtle / cards |
Icon colour utilities
Apply these classes directly to any Lucide <svg>. Use the parent .icon-hover--* classes to change icon colour on hover.
<!-- Colour class on the svg --> <svg data-lucide="check-circle" width="20" height="20" class="icon--ok"></svg> <svg data-lucide="alert-triangle" width="20" height="20" class="icon--warn"></svg> <!-- Icon button variants --> <button class="icon-btn"><svg data-lucide="copy" width="16" height="16"></svg></button> <button class="icon-btn icon-btn--blue"><svg data-lucide="download" width="16" height="16"></svg></button> <button class="icon-btn icon-btn--danger"><svg data-lucide="trash-2" width="16" height="16"></svg></button> <!-- Hover: change icon colour when parent is hovered --> <a class="icon-hover--blue" href="/"> <svg data-lucide="home" width="18" height="18"></svg> Home </a>
Responsive Preview
Live preview of components at each breakpoint. Each frame loads a lightweight component sampler constrained to its breakpoint width.
How to Use
CDN
<link rel="stylesheet" href="https://design.romanspizza.co.za/rp-style.css"> <script src="lucide.min.js"></script> <script>lucide.createIcons();</script>
npm
npm install lucide rp-design-system
// In your JS entry:
import { createIcons, icons } from 'lucide';
import 'rp-design-system/dist/rp-style.css';
createIcons({ icons });Icon usage
<!-- Add a data-lucide attribute, then call createIcons() once --> <svg data-lucide="star" width="20" height="20"></svg> <button class="rp-btn rp-btn--primary"> <svg data-lucide="download" width="14" height="14"></svg> Download </button>