16 KiB
name: css-framework description: Erstellt und bearbeitet HTML/CSS/JS für CSS-Framework — ein classless-first CSS-Framework für Jinja2/HTMX-Webapps. Verwende diesen Skill für alle UI-Aufgaben: Layouts, Navigation, Buttons, Cards, Formulare (inkl. Switch, File Upload), Tabellen (inkl. sortierbar, sticky, mobile-stack), Modals, Dropdowns, Tabs, Alerts (inkl. dismissible), Accordion, Filter Chips, Timeline, Stepper, List Group, Meta List, Tooltip, Toast, Pagination, Feedback-Komponenten (Spinner, Skeleton, Stat, Avatar) sowie Alignment- und Spacing-Utilities. Auch für Design Tokens, Dark Mode, HTMX-Integration und die JavaScript-API (showToast, dismissAlert, sortTable, openModal, switchTab, toggleDropdown). metadata: author: tom version: "2.1"
Überblick
CSS-Framework ist ein schlankes, tokenbasiertes CSS-Framework für Flask/Jinja2-Webapps mit HTMX. Kein Build-Tool, keine Abhängigkeiten.
Einbinden:
<script src="/static/js/theme.js"></script> <!-- im <head>, ohne defer -->
<link rel="stylesheet" href="/static/css/main.css">
<script src="/static/js/framework.js" defer></script>
Datei-Architektur:
| Datei | Zweck |
|---|---|
tokens.css |
CSS Custom Properties — einzige Source of Truth |
base.css |
Classless Reset — semantisches HTML braucht keine Klassen |
components.css |
Opt-in Klassen für Layouts & Komponenten |
utilities.css |
Minimale Helfer-Klassen |
main.css |
Entry Point |
framework.js |
JavaScript API — data-action und globale Funktionen |
theme.js |
Hell/Dunkel vor dem Rendern setzen und umschalten |
Vollständige Referenzen: tokens.md · components.md · jinja2.md
Kernprinzipien
- Classless first —
h1–h6,p,a,table,input,select,detailswerden ohne Klassen gestylt - Opt-in Klassen — nur wo semantisches HTML nicht ausreicht
- Token first — alle Werte über
tokens.css, nie hartverdrahten - Dark Mode via
data-theme="dark"auf<html> - Minimales JS — framework-unabhängig, in
framework.jsvia<script src>eingebunden - CSP-fest — Aktionen über
data-action, Styles über Klassen, kein Inline-Code
Design Tokens (Kurzübersicht)
/* Hintergründe */ --bg --bg-subtle --bg-muted
/* Surfaces */ --surface --surface-raised --surface-overlay
/* Rahmen */ --border --border-strong --border-focus
/* Text */ --text --text-muted --text-subtle --text-on-accent
/* Akzentfarbe */ --accent --accent-hover --accent-subtle --accent-muted
/* Status */ --color-success --color-warning --color-danger --color-info
/* Spacing (4px-Grid) */ --space-1(4px) … --space-24(96px)
/* Typografie */ --font-sans --font-mono | --text-xs … --text-4xl
Layout-Klassen
<div class="container"> <!-- max-width 1280px -->
<div class="container container-md"> <!-- max-width 768px -->
<div class="stack"> <!-- flex-col, gap 1rem -->
<div class="stack stack-sm"> <!-- gap 0.5rem -->
<div class="stack stack-lg"> <!-- gap 2rem -->
<div class="cluster"> <!-- flex-wrap, gap 0.75rem, align center -->
<div class="grid grid-2"> <!-- 2 Spalten -->
<div class="grid grid-3"> <!-- 3 Spalten -->
<div class="grid grid-4"> <!-- 4 Spalten -->
<div class="with-sidebar"> <!-- sidebar-width + 1fr -->
Komponenten (Schnellreferenz)
Vollständige Beispiele: references/components.md
Navigation
<nav class="topnav">...</nav> <!-- sticky, mit burger + dropdown -->
<aside class="sidebar">...</aside> <!-- mit .with-sidebar kombinieren -->
<nav class="breadcrumbs"><ol>...</ol></nav>
<footer class="footer">...</footer> <!-- .footer-full für mehrspaltig -->
Buttons
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-danger">Danger</button>
<!-- Größen: btn-xs btn-sm btn-lg btn-xl | Modifiers: btn-full btn-icon -->
<div class="button-group">...</div> <!-- bündige Gruppe -->
<div class="segmented">...</div> <!-- Radio-Basis, kein JS -->
Badges & Cards
<span class="badge badge-success badge-dot">Live</span>
<!-- badge-default/primary/success/warning/danger/info/outline -->
<div class="card">
<div class="card-header"><h3>Titel</h3></div>
<div class="card-body">...</div>
<div class="card-footer">...</div>
</div>
<!-- card-flat / card-raised / card-ghost / card-hover -->
Formulare
<div class="form-group">
<label for="x">Label</label>
<input type="text" id="x">
<span class="form-hint">Hinweis</span>
</div>
<div class="form-row">...</div> <!-- nebeneinander -->
<input class="error"> <span class="form-error"> <!-- Fehler-State -->
<div class="input-group"><span class="input-group-prefix">https://</span><input></div>
<label class="file-upload">...</label> <!-- .file-upload-dropzone -->
<label class="switch"><input class="switch-input" type="checkbox">...</label>
Tabellen
<table>...</table> <!-- classless -->
<div class="table-wrap"><table>...</table></div> <!-- mit Rahmen + Scroll -->
<table class="table-striped">
<table class="table-compact">
<table class="table-sticky"> <!-- sticky thead -->
<table class="table-sticky-col"> <!-- sticky erste Spalte -->
<table class="table-stack"> <!-- Mobile: Key-Value (data-label auf td) -->
<table class="table-sortable"> <!-- data-action="sort" data-sort="" auf th -->
Alerts & Feedback
<div class="alert alert-success">
<div class="alert-content"><p class="alert-title">Titel</p><p>Text</p></div>
<button class="alert-dismiss" data-action="alert-dismiss">✕</button>
</div>
<!-- alert-info / alert-success / alert-warning / alert-danger -->
<div class="spinner"></div> <!-- spinner-sm / spinner-lg -->
<div class="progress"><div class="progress-bar" data-progress="75"></div></div>
<div class="skeleton skeleton-line is-short"></div> <!-- auch skeleton-title, skeleton-block -->
<div class="stat"><span class="stat-label">...</span><span class="stat-value">...</span></div>
<div class="avatar">AM</div> <!-- avatar-sm / avatar-lg -->
<div class="empty"><p class="empty-title">...</p></div>
<div class="divider">oder</div>
Modals & Dropdowns & Tabs
<div class="modal-backdrop" id="m" data-action="modal-backdrop">
<div class="modal modal-lg">
<div class="modal-header">...</div>
<div class="modal-body">...</div>
<div class="modal-footer">...</div>
</div>
</div>
<button data-action="modal-open" data-target="m">Öffnen</button>
<!-- modal-sm / modal-lg / modal-xl -->
<div class="dropdown" id="dd"><button data-action="dropdown" data-target="dd">...</button>
<div class="dropdown-menu"><button class="dropdown-item">...</button></div>
</div>
<div class="tabs">
<ul class="tab-list" role="tablist">
<li><button class="active" role="tab" data-action="tab" data-target="p1">Tab</button></li>
</ul>
<div id="p1" class="tab-panel active">...</div>
</div>
Neue Komponenten
<!-- Filter Chips (Checkbox/Radio, kein JS) -->
<div class="chip-group">
<label class="chip"><input class="chip-input" type="checkbox" checked><span class="chip-label">Design</span></label>
</div>
<!-- Timeline -->
<div class="timeline">
<div class="timeline-item timeline-success">
<div class="timeline-marker"><div class="timeline-dot"></div><div class="timeline-connector"></div></div>
<div class="timeline-content"><p class="timeline-title">Titel</p><p class="timeline-meta">Meta</p></div>
</div>
</div>
<!-- timeline-accent / timeline-success / timeline-warning / timeline-danger -->
<!-- Stepper -->
<div class="stepper">
<div class="step step-done"><div class="step-indicator">✓</div><span class="step-label">Schritt</span></div>
<div class="step step-active"><div class="step-indicator">2</div><span class="step-label">Aktiv</span></div>
<div class="step"><div class="step-indicator">3</div><span class="step-label">Offen</span></div>
</div>
<!-- .stepper-vertical für vertikale Variante -->
<!-- List Group / Action List -->
<ul class="list-group">
<li class="list-item list-item-active">Aktiv</li>
<li class="list-item">Eintrag</li>
</ul>
<div class="action-list">
<a href="#" class="action-item">
<div class="action-item-content"><p class="action-item-title">Titel</p></div>
</a>
</div>
<!-- list-group-compact / action-list-flush -->
<!-- Meta List -->
<dl class="meta-list">
<div class="meta-item"><dt class="meta-label">Status</dt><dd class="meta-value">Aktiv</dd></div>
</dl>
<!-- meta-list-stacked / meta-list-compact / meta-list-flush -->
<!-- Tooltip (CSS-only) -->
<button data-tooltip="Erklärung">Hover</button>
<button data-tooltip="Unten" data-tooltip-pos="bottom">...</button>
<!-- data-tooltip-pos: top (default) / bottom / left / right -->
<!-- Accordion -->
<div class="accordion">
<details class="accordion-item" open>
<summary>Frage</summary>
<div class="accordion-body"><p>Antwort</p></div>
</details>
</div>
<!-- accordion-flush / accordion-muted -->
Utility-Klassen (Auswahl)
Display: block inline flex hidden
Flex: flex-center flex-between flex-start flex-end flex-col flex-wrap flex-1 flex-shrink-0
Alignment: items-start/center/end/stretch/baseline
justify-start/center/end/between
self-start/center/end/stretch
Gap: gap-1/2/3/4/6/8
Margin: mt-0/2/4/6/8/auto mb-0/2/4/6/8 ml-auto mr-auto mx-auto
Padding: p-0/2/4/6/8 px-4/6 py-4/6/8
Text: text-xs/sm/base/md/lg/xl text-left/center/right
text-muted text-subtle text-accent text-success text-warning text-danger
Font: font-normal/medium/semibold/bold font-mono uppercase capitalize truncate nowrap
Border: border border-t border-b rounded rounded-lg rounded-xl rounded-full
BG: bg-subtle bg-muted bg-surface
Shadow: shadow-xs shadow-sm shadow-md shadow-lg
Other: w-full h-full min-h-screen overflow-hidden overflow-auto overflow-x-auto
relative absolute sticky inset-0 cursor-pointer opacity-50 transition
filter-bar sr-only hide-mobile hide-desktop
JavaScript API
Aktionen deklarativ über data-action — CSP-fest, funktioniert auch in HTMX-Fragmenten. Nie onclick, style= oder Inline-<script> (Consumer-Apps laufen mit strenger CSP).
data-action |
Weitere Attribute | Wirkung | Entspricht |
|---|---|---|---|
modal-open |
data-target="id" |
Modal öffnen, Seite nicht mehr scrollen | openModal(id) |
modal-close |
data-target="id" (optional, sonst umgebendes Modal) |
Modal schließen | closeModal(id) |
modal-backdrop |
auf .modal-backdrop mit id |
Klick auf den Hintergrund schließt | closeOnBackdrop(e, id) |
tab |
data-target="panel-id" |
Tab wechseln | switchTab(btn, id) |
dropdown |
data-target="id" (optional, sonst umgebendes .dropdown) |
Dropdown öffnen/schließen | toggleDropdown(id) |
nav-dropdown |
– | Nav-Dropdown öffnen/schließen | toggleNavDd(btn) |
sidebar-open / sidebar-close |
– | Mobile Sidebar ein-/ausblenden | toggleSidebar() / closeSidebar() |
alert-dismiss |
– | Alert mit Transition entfernen | dismissAlert(btn) |
sort |
auf th, mit data-sort="" |
Spalte sortieren | sortTable(th) |
toggle-class |
data-class="checked" |
Klasse am Element umschalten | – |
toast |
data-message="…", data-type="success" |
Toast einblenden | showToast(msg, type) |
toast-close |
– | Toast schließen (setzt showToast selbst) |
removeToast(id) |
theme-toggle |
– | Hell/Dunkel umschalten (theme.js) |
– |
Für eigene Skripte gibt es dieselben Funktionen global: openModal(id), closeModal(id), switchTab(btn, id), toggleDropdown(id), showToast(msg, type), dismissAlert(btn), sortTable(th). showToast setzt die Meldung als Text (kein HTML).
Häufige Muster
Seitenstruktur mit Sidebar
<div class="mobile-topbar"><button data-action="sidebar-open">☰</button><span>App</span></div>
<div id="sidebar-backdrop" class="sidebar-backdrop" data-action="sidebar-close"></div>
<div class="with-sidebar">
<aside class="sidebar">...</aside>
<main class="stack stack-lg">...</main>
</div>
Dashboard-Kacheln
<div class="grid grid-3">
<div class="card"><div class="card-body">
<div class="stat"><span class="stat-label">Umsatz</span><span class="stat-value">€ 48.291</span></div>
</div></div>
</div>
Detailseite mit Meta-Infos
<div class="grid grid-2">
<div class="card"><div class="card-body">
<dl class="meta-list">
<div class="meta-item"><dt class="meta-label">Status</dt><dd class="meta-value"><span class="badge badge-success">Aktiv</span></dd></div>
</dl>
</div></div>
</div>
Aktionsliste mit Zeilen-Alignment
<div class="action-list">
<a href="/einstellungen" class="action-item">
<div class="action-item-content">
<p class="action-item-title">Einstellungen</p>
<p class="action-item-desc">Profil und Konto</p>
</div>
<svg class="action-item-end">›</svg>
</a>
</div>
Filter-Leiste
<div class="filter-bar">
<div class="form-group"><label>Monat</label><input type="month"></div>
<div class="chip-group"><label class="chip"><input class="chip-input" type="checkbox" checked><span class="chip-label">Aktiv</span></label></div>
<button class="btn btn-secondary btn-sm">Filtern</button>
</div>
HTMX-Muster
Vollständige Referenz inkl. Basis-Template und Flask-Patterns: references/jinja2.md
Button Loading State
<button class="btn btn-primary"
hx-post="/api/speichern"
hx-disabled-elt="this">
<span class="htmx-indicator spinner spinner-sm"></span>
Speichern
</button>
Inline-Validation (422)
Server antwortet mit HTTP 422 + rendert Formular mit .error-Klassen auf Inputs:
<form hx-post="/api/formular" hx-swap="outerHTML">
<div class="form-group">
<input type="email" name="email" class="error">
<span class="form-error">Ungültige E-Mail</span>
</div>
</form>
Skeleton als Ladeindikator
<div hx-get="/api/daten" hx-trigger="load" hx-swap="outerHTML">
<div class="stack">
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-line"></div>
</div>
</div>
Modal via HTMX befüllen
<button class="btn btn-secondary"
hx-get="/api/detail/{{ id }}"
hx-target="#modal-body"
hx-swap="innerHTML"
data-action="modal-open" data-target="m">Details</button>
<div class="modal-backdrop" id="m" data-action="modal-backdrop">
<div class="modal">
<div class="modal-body" id="modal-body">
<div class="skeleton skeleton-block"></div>
</div>
</div>
</div>
Toast über HX-Trigger-Header
# Flask: HX-Trigger im Response-Header setzen
response.headers['HX-Trigger'] = '{"showToast":{"message":"Gespeichert","type":"success"}}'
<!-- body-Attribut im Basis-Template -->
<body hx-on::after-request="
var h=event.detail.xhr.getResponseHeader('HX-Trigger');
if(h){try{var t=JSON.parse(h);if(t.showToast)showToast(t.showToast.message,t.showToast.type);}catch(e){}}
">