Files
zitadel/apps/login/THEME_ARCHITECTURE.md
T
Max PeintnerandGitHub 434aeb275a feat(login): comprehensive theme system (#10848)
# Which Problems Are Solved

This PR introduces a comprehensive theme customization system for the
login application with responsive behavior and enhanced visual options.

<img width="1122" height="578" alt="Screenshot 2025-08-19 at 09 55 24"
src="https://github.com/user-attachments/assets/cdcc8948-533d-4e13-bf45-fdcc24acfb2b"
/>

# How the Problems Are Solved

## ✨ Features Added

- **🔄 Responsive Layout System**: Automatic switching between
side-by-side and top-to-bottom layouts based on screen size
- **🖼️ Background Image Support**: Custom background images configurable
via environment variables
- **⚙️ Theme Configuration**: Complete theme system with roundness,
spacing, appearance, and layout options
- **📱 Mobile-First Design**: Intelligent layout adaptation for different
screen sizes
- **🎯 Enhanced Typography**: Improved visual hierarchy with larger
titles in side-by-side mode

## 🏗️ Architecture

- **Server-Safe Theme Functions**: Theme configuration accessible on
both server and client
- **SSR-Safe Hooks**: Proper hydration handling for responsive layouts
- **Component Separation**: Clear boundaries between server and client
components
- **Two-Section Layout**: Consistent content structure across all login
pages

## 🔧 Configuration Options

All theme options are configurable via environment variables:

- `NEXT_PUBLIC_THEME_ROUNDNESS`: `edgy` | `mid` | `full`
- `NEXT_PUBLIC_THEME_LAYOUT`: `side-by-side` | `top-to-bottom`
- `NEXT_PUBLIC_THEME_APPEARANCE`: `flat` | `material`
- `NEXT_PUBLIC_THEME_SPACING`: `regular` | `compact`
- `NEXT_PUBLIC_THEME_BACKGROUND_IMAGE`: Custom background image URL

## 📄 Pages Updated

Updated all major login pages to use the new two-section responsive
layout:
- Login name entry
- Password verification
- MFA verification
- User registration
- Account selection
- Device authorization
- Logout confirmation

## 📚 Documentation

- **THEME_ARCHITECTURE.md**: Complete technical documentation of the
theme system
- **THEME_CUSTOMIZATION.md**: User-friendly guide with examples and
troubleshooting

## 🚀 Benefits

- **Better UX**: Responsive design that works seamlessly across all
devices
- **Brand Flexibility**: Easy customization to match any brand identity
- **Maintainable Code**: Clean separation of concerns and
well-documented architecture
- **Future-Proof**: Extensible system for additional theme options


<img width="580" height="680" alt="Screenshot 2025-08-19 at 09 22 23"
src="https://github.com/user-attachments/assets/9de8da37-6d56-4fe9-b337-5d8ad2a3ba59"
/>
<img width="599" height="689" alt="Screenshot 2025-08-19 at 09 23 45"
src="https://github.com/user-attachments/assets/26a30cc7-4017-4f4b-8b87-a49466c42b94"
/>
<img width="595" height="681" alt="Screenshot 2025-08-19 at 09 23 17"
src="https://github.com/user-attachments/assets/a3d31088-4545-4f36-aafe-1aae1253d677"
/>
2025-10-10 10:26:06 +02:00

6.3 KiB

Current Theme System Architecture

Our theme system provides a simple, environment variable-driven approach for consistent component styling and responsive layout switching.

🏗️ Current Implementation

Environment Variable Configuration

# .env.local
NEXT_PUBLIC_THEME_ROUNDNESS=mid          # edgy | mid | full
NEXT_PUBLIC_THEME_LAYOUT=side-by-side    # side-by-side | top-to-bottom
NEXT_PUBLIC_THEME_APPEARANCE=material    # flat | material
NEXT_PUBLIC_THEME_SPACING=regular        # regular | compact

Core Theme Functions

// Server-safe theme configuration
import { getThemeConfig, getComponentRoundness } from "@/lib/theme";

// Get full theme configuration
const themeConfig = getThemeConfig();
// Returns: { roundness: 'mid', layout: 'side-by-side', appearance: 'material', ... }

// Get component-specific styling
const buttonRoundness = getComponentRoundness("button");
// Returns: "rounded-md" (CSS class)

Responsive Layout Hook

// Client-side responsive layout detection
import { useResponsiveLayout } from "@/lib/theme-hooks";

function MyComponent() {
  const { isSideBySide, isResponsiveOverride } = useResponsiveLayout();

  return <div className={isSideBySide ? "flex" : "flex-col"}>{/* Layout adapts automatically */}</div>;
}

🎨 Component Integration Patterns

Pattern 1: Direct Function Calls (Current Standard)

import { getComponentRoundness } from "@/lib/theme";

export function Button({ children, variant = "primary" }) {
  const roundness = getComponentRoundness("button");

  return (
    <button className={`${roundness} px-4 py-2 ${variant === "primary" ? "bg-blue-500" : "bg-gray-500"}`}>{children}</button>
  );
}

Pattern 2: Component-Specific Helper Functions

import { getComponentRoundness } from "@/lib/theme";

// Helper function for UserAvatar
function getUserAvatarRoundness(): string {
  return getComponentRoundness("avatarContainer");
}

export function UserAvatar({ loginName, displayName }) {
  const roundness = getUserAvatarRoundness();

  return <div className={`flex border p-1 ${roundness}`}>{/* Avatar content */}</div>;
}

Pattern 3: Theme-Aware Layout Components

import { useResponsiveLayout } from "@/lib/theme-hooks";

export function DynamicTheme({ children, branding }) {
  const { isSideBySide } = useResponsiveLayout();

  return (
    <ThemeWrapper branding={branding}>
      {isSideBySide ? (
        // Side-by-side layout for desktop
        <div className="flex max-w-[1200px]">
          <div className="w-1/2">{/* Left content */}</div>
          <div className="w-1/2">{/* Right content */}</div>
        </div>
      ) : (
        // Top-to-bottom layout for mobile
        <div className="flex-col max-w-[440px]">{children}</div>
      )}
    </ThemeWrapper>
  );
}

🎯 Theme Configuration Structure

Component Roundness Mapping

export interface ComponentRoundnessConfig {
  card: ThemeRoundness; // "rounded-lg" | "rounded-none" | "rounded-3xl"
  button: ThemeRoundness; // "rounded-md" | "rounded-none" | "rounded-full"
  input: ThemeRoundness; // "rounded-md" | "rounded-none" | "rounded-full pl-4"
  image: ThemeRoundness; // "rounded-lg" | "rounded-none" | "rounded-full"
  avatar: ThemeRoundness; // "rounded-lg" | "rounded-none" | "rounded-full"
  avatarContainer: ThemeRoundness; // "rounded-md" | "rounded-none" | "rounded-full"
  themeSwitch: ThemeRoundness; // "rounded-md" | "rounded-none" | "rounded-full"
}

Responsive Layout Logic

// Automatic layout switching based on screen size
const isSideBySide = themeConfig.layout === "side-by-side" && !isMdOrSmaller;

// md breakpoint: 768px (Tailwind default)
// Below 768px: Always use top-to-bottom layout
// Above 768px: Use configured layout (side-by-side or top-to-bottom)

� File Structure

src/lib/
├── theme.ts           # Server-safe theme functions
├── theme-hooks.ts     # Client-side responsive hooks
└── themeUtils.tsx     # Legacy utility functions

src/components/
├── dynamic-theme.tsx  # Main responsive layout component
├── theme-wrapper.tsx  # Theme application wrapper
├── button.tsx         # Example themed component
├── card.tsx          # Example themed component
└── user-avatar.tsx   # Example themed component

� Usage Examples

Adding Theme Support to New Components

import { getComponentRoundness } from "@/lib/theme";

export function NewComponent() {
  // Get theme-appropriate styling
  const roundness = getComponentRoundness("card");

  return <div className={`p-4 ${roundness} bg-white`}>{/* Component content */}</div>;
}

Using Responsive Layout

import { useResponsiveLayout } from "@/lib/theme-hooks";

export function ResponsiveComponent() {
  const { isSideBySide } = useResponsiveLayout();

  return <div className={isSideBySide ? "text-left" : "text-center"}>Content adapts to layout</div>;
}

Page Layout Integration

import { DynamicTheme } from "@/components/dynamic-theme";

export default function LoginPage() {
  return (
    <DynamicTheme branding={branding}>
      <div className="flex flex-col space-y-4">
        <h1>Login Title</h1>
        <p>Description text</p>
      </div>

      <div className="w-full">
        <LoginForm />
      </div>
    </DynamicTheme>
  );
}

⚡ Key Features

  1. Environment Variable Configuration: Simple .env.local setup
  2. Server-Safe Functions: Work in both SSR and client components
  3. Responsive Layout Switching: Automatic mobile/desktop adaptation
  4. Component-Specific Styling: Different roundness per component type
  5. Type Safety: Full TypeScript support
  6. Zero Runtime Dependencies: No context providers or complex state
  7. SSR Compatible: No hydration mismatches

🔄 Architecture Benefits

  • Simple: Environment variables → CSS classes
  • Fast: No runtime theme calculations or context switching
  • Reliable: Server-side rendering compatible
  • Scalable: Easy to add new theme properties
  • Maintainable: Clear separation between layout and styling concerns

This architecture provides a solid foundation for environment-driven theming while keeping the implementation simple and performant!