aRustyDev / aRustyDev/mdbook-htmx
docs(adr): CSS Architecture
- Dominant language
- Rust
- Stars
- 0
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
# CSS Architecture
This document describes the CSS architecture for mdbook-htmx, including the modular file structure, CSS custom properties (variables) system, and theming approach.
## Overview
mdbook-htmx uses a modular CSS architecture with:
- **CSS Custom Properties** for theming
- **Modular file organization** for maintainability
- **HTMX-specific styles** for transitions and indicators
- **PostCSS** for build-time processing
---
## File Structure
```
book/assets/css/
├── base/
│ ├── reset.css # CSS reset/normalize
│ ├── variables.css # CSS custom properties
│ └── typography.css # Typography rules
├── layout/
│ ├── grid.css # Grid system
│ ├── sidebar.css # Sidebar layout
│ └── content.css # Content area
├── components/
│ ├── buttons.css # Button styles
│ ├── cards.css # Card components
│ ├── alerts.css # Alert/notification styles
│ └── search.css # Search input/results
├── htmx/
│ ├── indicators.css # Loading indicators
│ └── transitions.css # Swap transitions
├── themes/
│ ├── light.css # Light theme variables
│ └── dark.css # Dark theme variables
├── docs.css # Main entry point (imports all)
└── postcss.config.js # PostCSS configuration
```
---
## CSS Custom Properties
### Core Variables (`base/variables.css`)
```css
:root {
/* Colors - Semantic */
--color-text: var(--color-gray-900);
--color-text-muted: var(--color-gray-600);
--color-text-inverse: var(--color-white);
--color-background: var(--color-white);
--color-background-alt: var(--color-gray-50);
--color-border: var(--color-gray-200);
--color-primary: var(--color-blue-600);
--color-primary-hover: var(--color-blue-700);
--color-success: var(--color-green-600);
--color-warning: var(--color-yellow-600);
--color-error: var(--color-red-600);
/* Colors - Palette */
--color-white: #ffffff;
--color-black: #000000;
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-gray-400: #9ca3af;
--color-gray-500: #6b7280;
--color-gray-600: #4b5563;
--color-gray-700: #374151;
--color-gray-800: #1f2937;
--color-gray-900: #111827;
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-blue-700: #1d4ed8;
--color-green-600: #16a34a;
--color-yellow-600: #ca8a04;
--color-red-600: #dc2626;
/* Typography */
--font-family-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-family-mono: "JetBrains Mono", "Fira Code", Consolas, monospace;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 1.875rem;
--font-size-4xl: 2.25rem;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* Spacing */
--spacing-0: 0;
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-3: 0.75rem;
--spacing-4: 1rem;
--spacing-5: 1.25rem;
--spacing-6: 1.5rem;
--spacing-8: 2rem;
--spacing-10: 2.5rem;
--spacing-12: 3rem;
--spacing-16: 4rem;
/* Layout */
--sidebar-width: 280px;
--sidebar-width-collapsed: 60px;
--content-max-width: 800px;
--header-height: 60px;
--border-radius-sm: 0.25rem;
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
--border-radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
/* Transitions */
--transition-fast: 150ms ease;
--transition-normal: 200ms ease;
--transition-slow: 300ms ease;
/* Z-index scale */
--z-dropdown: 100;
--z-sticky: 200;
--z-modal: 300;
--z-tooltip: 400;
}
```
---
## Theme System
### Light Theme (`themes/light.css`)
```css
:root,
[data-theme="light"] {
--color-text: var(--color-gray-900);
--color-text-muted: var(--color-gray-600);
--color-background: var(--color-white);
--color-background-alt: var(--color-gray-50);
--color-border: var(--color-gray-200);
--color-sidebar-bg: var(--color-gray-50);
--color-code-bg: var(--color-gray-100);
}
```
### Dark Theme (`themes/dark.css`)
```css
[data-theme="dark"] {
--color-text: var(--color-gray-100);
--color-text-muted: var(--color-gray-400);
--color-background: var(--color-gray-900);
--color-background-alt: var(--color-gray-800);
--color-border: var(--color-gray-700);
--color-sidebar-bg: var(--color-gray-800);
--color-code-bg: var(--color-gray-800);
}
```
### Theme Switching
```html
Toggle Theme
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute("data-theme");
html.setAttribute("data-theme", current === "dark" ? "light" : "dark");
localStorage.setItem("theme", html.getAttribute("data-theme"));
}
// Respect system preference
if (!localStorage.getItem("theme")) {
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
document.documentElement.setAttribute(
"data-theme",
prefersDark ? "dark" : "light"
);
}
```
---
## HTMX-Specific Styles
### Loading Indicators (`htmx/indicators.css`)
```css
/* Global loading indicator */
.htmx-indicator {
display: none;
opacity: 0;
transition: opacity var(--transition-normal);
}
.htmx-request .htmx-indicator {
display: inline-block;
opacity: 1;
}
/* Spinner */
.htmx-indicator.spinner {
width: 1rem;
height: 1rem;
border: 2px solid var(--color-border);
border-top-color: var(--color-primary);
border-radius: var(--border-radius-full);
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Progress bar */
.htmx-indicator.progress-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(
90deg,
var(--color-primary) 0%,
var(--color-primary) 30%,
transparent 30%
);
animation: progress 1s ease-in-out infinite;
}
@keyframes progress {
0% {
background-position: -100% 0;
}
100% {
background-position: 200% 0;
}
}
/* Skeleton loading */
.skeleton {
background: linear-gradient(
90deg,
var(--color-background-alt) 25%,
var(--color-border) 50%,
var(--color-background-alt) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
```
### Swap Transitions (`htmx/transitions.css`)
```css
/* Fade transition */
.htmx-swapping {
opacity: 0;
transition: opacity var(--transition-normal);
}
.htmx-settling {
opacity: 1;
transition: opacity var(--transition-normal);
}
/* Slide transition */
.htmx-swapping.slide-out {
transform: translateX(-10px);
opacity: 0;
transition: all var(--transition-normal);
}
.htmx-settling.slide-in {
transform: translateX(0);
opacity: 1;
transition: all var(--transition-normal);
}
/* Content swap animation */
#content.htmx-swapping > * {
opacity: 0.5;
transition: opacity var(--transition-fast);
}
#content.htmx-settling > * {
opacity: 1;
transition: opacity var(--transition-normal);
}
/* Disable transitions for reduced motion preference */
@media (prefers-reduced-motion: reduce) {
.htmx-swapping,
.htmx-settling,
.htmx-indicator {
transition: none;
animation: none;
}
}
```
---
## Component Styles
### Buttons (`components/buttons.css`)
```css
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-2);
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
border-radius: var(--border-radius-md);
border: 1px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-primary {
background: var(--color-primary);
color: var(--color-text-inverse);
}
.btn-primary:hover {
background: var(--color-primary-hover);
}
.btn-secondary {
background: transparent;
color: var(--color-text);
border-color: var(--color-border);
}
.btn-secondary:hover {
background: var(--color-background-alt);
}
/* Loading state */
.btn.htmx-request {
pointer-events: none;
opacity: 0.7;
}
```
### Alerts (`components/alerts.css`)
```css
.alert {
padding: var(--spacing-4);
border-radius: var(--border-radius-md);
border: 1px solid;
}
.alert-info {
background: var(--color-blue-50, #eff6ff);
border-color: var(--color-blue-200, #bfdbfe);
color: var(--color-blue-800, #1e40af);
}
.alert-success {
background: var(--color-green-50, #f0fdf4);
border-color: var(--color-green-200, #bbf7d0);
color: var(--color-green-800, #166534);
}
.alert-warning {
background: var(--color-yellow-50, #fefce8);
border-color: var(--color-yellow-200, #fef08a);
color: var(--color-yellow-800, #854d0e);
}
.alert-error {
background: var(--color-red-50, #fef2f2);
border-color: var(--color-red-200, #fecaca);
color: var(--color-red-800, #991b1b);
}
/* Access denied variant */
.alert-access-denied {
text-align: center;
padding: var(--spacing-8);
}
.alert-access-denied .icon {
font-size: var(--font-size-4xl);
margin-bottom: var(--spacing-4);
}
```
### Search (`components/search.css`)
```css
.search-container {
position: relative;
}
.search-input {
width: 100%;
padding: var(--spacing-2) var(--spacing-4);
padding-left: var(--spacing-10);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
background: var(--color-background);
color: var(--color-text);
font-size: var(--font-size-sm);
}
.search-input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
}
.search-icon {
position: absolute;
left: var(--spacing-3);
top: 50%;
transform: translateY(-50%);
color: var(--color-text-muted);
}
.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: var(--spacing-2);
background: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-lg);
max-height: 400px;
overflow-y: auto;
z-index: var(--z-dropdown);
}
.search-result-item {
padding: var(--spacing-3) var(--spacing-4);
border-bottom: 1px solid var(--color-border);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item:hover {
background: var(--color-background-alt);
}
.search-result-title {
font-weight: var(--font-weight-medium);
color: var(--color-text);
}
.search-result-excerpt {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
margin-top: var(--spacing-1);
}
.search-result-highlight {
background: var(--color-yellow-200, #fef08a);
padding: 0 2px;
border-radius: 2px;
}
```
---
## Layout Styles
### Grid System (`layout/grid.css`)
```css
.container {
display: grid;
grid-template-columns: var(--sidebar-width) 1fr;
min-height: 100vh;
}
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
}
```
### Sidebar (`layout/sidebar.css`)
```css
.sidebar {
position: sticky;
top: var(--header-height);
height: calc(100vh - var(--header-height));
overflow-y: auto;
padding: var(--spacing-4);
background: var(--color-sidebar-bg);
border-right: 1px solid var(--color-border);
}
.sidebar-nav a {
display: block;
padding: var(--spacing-2) var(--spacing-3);
color: var(--color-text);
text-decoration: none;
border-radius: var(--border-radius-md);
}
.sidebar-nav a:hover {
background: var(--color-background-alt);
}
.sidebar-nav a.active {
background: var(--color-primary);
color: var(--color-text-inverse);
}
/* Collapsible sections */
.sidebar-section {
margin-bottom: var(--spacing-2);
}
.sidebar-section-toggle {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: var(--spacing-2);
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.sidebar-section-toggle .chevron {
transition: transform var(--transition-fast);
}
.sidebar-section.collapsed .chevron {
transform: rotate(-90deg);
}
.sidebar-section.collapsed .sidebar-section-content {
display: none;
}
```
---
## PostCSS Configuration
```js
// postcss.config.js
module.exports = {
plugins: [
require("postcss-import"),
require("postcss-nesting"),
require("autoprefixer"),
process.env.NODE_ENV === "production" && require("cssnano"),
].filter(Boolean),
};
```
---
## Main Entry Point
```css
/* docs.css */
@import "base/reset.css";
@import "base/variables.css";
@import "base/typography.css";
@import "themes/light.css";
@import "themes/dark.css";
@import "layout/grid.css";
@import "layout/sidebar.css";
@import "layout/content.css";
@import "components/buttons.css";
@import "components/cards.css";
@import "components/alerts.css";
@import "components/search.css";
@import "htmx/indicators.css";
@import "htmx/transitions.css";
```
---
## Related Documentation
- [ADR-0020: Theming Architecture](../adr/0020-theming-architecture.md)
- [ADR-0018: Asset Hashing and Cache Busting](../adr/0018-asset-hashing-and-cache-busting.md)
Contributor guide
Assessment
This issue has not been assessed yet.