Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

docs(adr): CSS Architecture

Đang mở
#30 0 bình luận 0 reaction 1 người được giao Xem trên GitHub

@aRustyDev đang làm issue này rồi.

Từ ngày 5/1/2026.

Đánh giá

Issue này chưa được đánh giá.

Mô tả

documentation

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)
: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)
: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)
[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 data-theme="light">
  <body>
    <button onclick="toggleTheme()">Toggle Theme</button>
  </body>
</html>

<script>
  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"
    );
  }
</script>

HTMX-Specific Styles

Loading Indicators (htmx/indicators.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)
/* 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)
.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)
.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)
.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)
.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)
.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

// 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

/* 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

Ngôn ngữ chính
Rust
Star
0
Fork
1
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của aRustyDev/mdbook-htmx

Tất cả issue của aRustyDev/mdbook-htmx

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.