apps/login/THEME_ARCHITECTURE.md
Our theme system provides a simple, environment variable-driven approach for consistent component styling and responsive layout switching.
# .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
// Server-safe theme configuration
import { getThemeConfig, getComponentRoundness } from "@/lib/theme";
// Get full theme settings
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)
// Client-side responsive layout detection
import { useResponsiveLayout } from "@/lib/theme-hooks";
function MyComponent() {
const { isSideBySide, isResponsiveOverride } = useResponsiveLayout();
return <div className={isSideBySide ? "flex" : "flex-col"}></div>;
}
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>
);
}
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}`}></div>;
}
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"></div>
<div className="w-1/2"></div>
</div>
) : (
// Top-to-bottom layout for mobile
<div className="flex-col max-w-[440px]">{children}</div>
)}
</ThemeWrapper>
);
}
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"
}
// 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)
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
import { getComponentRoundness } from "@/lib/theme";
export function NewComponent() {
// Get theme-appropriate styling
const roundness = getComponentRoundness("card");
return <div className={`p-4 ${roundness} bg-white`}></div>;
}
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>;
}
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-
);
}
.env.local setupThis architecture provides a solid foundation for environment-driven theming while keeping the implementation simple and performant!