Styling Approach
KDS Frontend uses SCSS Modules for component styling. This approach provides:
Scoped Styles CSS classes are automatically scoped to prevent naming conflicts
Type Safety TypeScript integration for CSS class names
CSS Variables Dynamic theming with CSS custom properties
SCSS Features Mixins, variables, and nesting for DRY styles
SCSS Modules
How It Works
Each component has a corresponding .module.scss file that is imported as a JavaScript object.
OrderCard.tsx
OrderCard.module.scss
import s from './OrderCard.module.scss' ;
export default function OrderCard ({ order } : OrderCardProps ) {
return (
< div className = {s. card } >
< div className = {s. content } >
< div className = {s. partnerName } > {order. partnerName } </ div >
< div className = {s. orderNumber } > Order : { order . displayNumber }</ div >
</ div >
</ div >
);
}
Notice the .module.scss extension. This tells Next.js to process the file as a CSS Module, generating unique class names like OrderCard_card__a1b2c3.
Combining Multiple Classes
Use the classnames (aliased as clsx) library to conditionally combine classes:
Dynamic classes
Status-specific styles
import clsx from 'classnames' ;
import s from './OrderCard.module.scss' ;
const STATUS_CLASS : Record < OrderStatus , string | undefined > = {
RECEIVED: s . received ,
CONFIRMED: s . confirmed ,
PREPARING: s . preparing ,
READY: s . ready ,
PICKED_UP: s . pickedUp ,
DELIVERED: s . delivered ,
CANCELLED: s . cancelled ,
};
export default function OrderCard ({ order } : OrderCardProps ) {
const isHighPriority = order . priority === 'HIGH' ;
return (
< Card
className = { clsx (
s.card,
STATUS_CLASS [ order . status ],
isHighPriority && s.priorityHigh
)}
>
{ /* ... */ }
</ Card >
);
}
Design Tokens (CSS Variables)
All design tokens are defined in styles/globals.scss as CSS custom properties. This enables runtime theming without rebuilding.
Color Tokens
:root {
/* Backgrounds */
--bg-app : #f8f9fb ;
--bg-surface : #ffffff ;
--bg-column : #f4f5f7 ;
--bg-modal : #ffffff ;
/* Text */
--text-primary : #1f2937 ;
--text-secondary : #6b7280 ;
--text-muted : #666 ;
--text-label : #777 ;
/* Borders & Dividers */
--border-default : #dfe1e6 ;
--divider : #e5e5e5 ;
/* Shadows */
--shadow-default : 0 8 px 20 px rgba ( 0 , 0 , 0 , 0.08 );
--shadow-modal : 0 25 px 60 px rgba ( 0 , 0 , 0 , 0.35 );
--shadow-rider : 0 1 px 4 px rgba ( 0 , 0 , 0 , 0.08 );
}
Status Colors
Each order status has its own background color:
:root {
--status-received : #f0f4ff ; /* Light blue */
--status-confirmed : #f2fbf7 ; /* Light green */
--status-preparing : #fff9f1 ; /* Light amber */
--status-ready : #f3fff8 ; /* Light mint */
--status-pickedup : #f5f3ff ; /* Light violet */
--status-delivered : #f1f5f9 ; /* Light slate */
--status-cancelled : #fff1f3 ; /* Light red */
}
html .dark {
--status-received : #1e293b ; /* Dark blue */
--status-confirmed : #0f2f24 ; /* Dark green */
--status-preparing : #3a2e12 ; /* Dark amber */
--status-ready : #0f3328 ; /* Dark mint */
--status-pickedup : #2a1f3d ; /* Dark violet */
--status-delivered : #1f2937 ; /* Dark slate */
--status-cancelled : #3a1a1f ; /* Dark red */
}
Priority Colors
:root {
--priority-normal-bg : #eef2ff ;
--priority-normal-text : #3730a3 ;
--priority-high-bg : #fff7ed ;
--priority-high-text : #9a3412 ;
}
html .dark {
--priority-normal-bg : #1e293b ;
--priority-normal-text : #c7d2fe ;
--priority-high-bg : #3f2a0f ;
--priority-high-text : #fdba74 ;
}
Layout Tokens
:root {
--max-width : 1100 px ;
--border-radius : 12 px ;
}
Why use CSS variables over SCSS variables?
CSS variables can be changed at runtime , enabling dynamic theming. SCSS variables are compiled at build time and cannot change based on user preferences.
Theme System
The application supports light and dark modes using a class-based approach .
How It Works
Theme Provider
The ThemeProvider context manages theme state and persists it to localStorage: contexts/Theme.context.tsx
export function ThemeProvider ({ children } : { children : React . ReactNode }) {
const [ theme , setTheme ] = useState < Theme >( "light" );
useEffect (() => {
const html = document . documentElement ;
if ( theme === "dark" ) {
html . classList . add ( "dark" );
} else {
html . classList . remove ( "dark" );
}
localStorage . setItem ( "theme" , theme );
}, [ theme ]);
const toggleTheme = () =>
setTheme ( prev => ( prev === "light" ? "dark" : "light" ));
return (
< ThemeContext . Provider value = {{ theme , toggleTheme }} >
{ children }
</ ThemeContext . Provider >
);
}
Dark class on <html>
When dark mode is active, the dark class is added to the <html> element. This cascades down to all CSS variable definitions.
CSS variables update
All html.dark CSS variables override the root values, instantly changing the entire application’s appearance.
Using the Theme Context
Access and toggle the theme from any component:
components/theme/ThemeToggle.tsx
import { useTheme } from '@/contexts/Theme.context' ;
export default function ThemeToggle () {
const { theme , toggleTheme } = useTheme ();
return (
< button onClick = { toggleTheme } >
{ theme === ' light ' ? '🌙 Dark' : '☀️ Light' }
</ button >
);
}
Theme preference is automatically saved to localStorage and restored on subsequent visits.
SCSS Variables and Mixins
The styles/variables.scss file contains build-time SCSS variables and mixins:
Color palette
Responsive breakpoints
// Brand colors
$cta : #ae191a ;
$secondary : #faf2ee ;
$tertiary : #05a450 ;
// Grayscale
$white : #ffffff ;
$black : #000000 ;
$gray-100 : #191919 ;
$gray-200 : #323232 ;
// ... up to gray-1000
Using Responsive Mixins
Apply responsive styles with the @include respond() mixin:
html {
font-size : 16 px ;
@include respond (sm) {
font-size : 14 px ;
}
}
This compiles to:
html {
font-size : 16 px ;
}
@media ( max-width : 768 px ) {
html {
font-size : 14 px ;
}
}
Global Styles
The styles/globals.scss file includes global resets and base styles:
HTML/Body reset
Box model reset
Smooth transitions
html ,
body {
height : 100 % ;
margin : 0 ;
padding : 0 ;
overflow : hidden ;
}
body {
min-height : 100 dvh ; // Modern vh unit for mobile
background : var ( --bg-app );
color : var ( --text-primary );
}
The 100dvh unit is used instead of 100vh to avoid issues on mobile browsers where the address bar affects viewport height.
Component Styling Example
Here’s a complete example showing all styling concepts:
import clsx from 'classnames' ;
import s from './OrderCard.module.scss' ;
import type { OrderListDto } from '@/dtos/OrderList.dto' ;
import { OrderStatus } from '@/domain/order/order-status' ;
const STATUS_CLASS : Record < OrderStatus , string | undefined > = {
RECEIVED: s . received ,
CONFIRMED: s . confirmed ,
PREPARING: s . preparing ,
READY: s . ready ,
PICKED_UP: s . pickedUp ,
DELIVERED: s . delivered ,
CANCELLED: s . cancelled ,
};
type OrderCardProps = {
order : OrderListDto ;
onClick ?: () => void ;
};
export default function OrderCard ({ order , onClick } : OrderCardProps ) {
const isHighPriority = order . priority === "HIGH" ;
return (
< div
className = { clsx (
s.card,
STATUS_CLASS [ order . status ]
)}
onClick = { onClick }
>
< div className = {s. content } >
< div className = {s. partnerName } > {order. partnerName } </ div >
< div className = {s. orderNumber } > Order : { order . displayNumber }</ div >
< span
className = { clsx (
s.priority,
isHighPriority ? s.priorityHigh : s . priorityNormal
)}
>
{ isHighPriority ? "High" : "Normal" }
</ span >
</ div >
</ div >
);
}
Best Practices
Use CSS Variables Always use CSS variables for colors, spacing, and shadows to support theming
Scope Styles Keep styles scoped to components using .module.scss files
Semantic Class Names Use descriptive class names like .partnerName instead of .text-bold
Responsive by Default Use the @include respond() mixin for mobile-first responsive design
Inline styles cannot be overridden by themes and hurt performance. Use CSS classes instead.
Always use CSS variables so themes work correctly: /* ❌ Bad */
.card {
background : #ffffff ;
color : #1f2937 ;
}
/* ✅ Good */
.card {
background : var ( --bg-surface );
color : var ( --text-primary );
}
Import variables when needed
If you need SCSS variables or mixins, import them: @import "/styles/variables.scss" ;
.container {
@include respond (sm) {
padding : 8 px ;
}
}
Debugging Styles
Inspect generated class names
CSS Modules generate unique class names. Use browser DevTools to see the compiled class:
Check CSS variable values
In DevTools, inspect the :root or html.dark selector to see current CSS variable values.
Verify theme class
Ensure the <html> element has the dark class when dark mode is active.
Next Steps
Setup Guide Learn how to install and run the project
Project Structure Understand the architecture and directory organization