Skip to main content

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.
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:
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 8px 20px rgba(0, 0, 0, 0.08);
  --shadow-modal: 0 25px 60px rgba(0, 0, 0, 0.35);
  --shadow-rider: 0 1px 4px rgba(0, 0, 0, 0.08);
}

Status Colors

Each order status has its own background color:
Status colors
: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

Priority badges
: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

Spacing and sizing
:root {
  --max-width: 1100px;
  --border-radius: 12px;
}
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

1

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>
  );
}
2

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.
3

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:
// 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:
Responsive font sizing
html {
  font-size: 16px;

  @include respond(sm) {
    font-size: 14px;
  }
}
This compiles to:
Compiled CSS
html {
  font-size: 16px;
}

@media (max-width: 768px) {
  html {
    font-size: 14px;
  }
}

Global Styles

The styles/globals.scss file includes global resets and base styles:
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

body {
  min-height: 100dvh; // 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);
}
If you need SCSS variables or mixins, import them:
@import "/styles/variables.scss";

.container {
  @include respond(sm) {
    padding: 8px;
  }
}

Debugging Styles

1

Inspect generated class names

CSS Modules generate unique class names. Use browser DevTools to see the compiled class:
OrderCard_card__a1b2c3
2

Check CSS variable values

In DevTools, inspect the :root or html.dark selector to see current CSS variable values.
3

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

Build docs developers (and LLMs) love