Diese Seite dokumentiert die wiederverwendbaren Bausteine der App. Halte dich an diese Vorgaben, damit alle Ansichten einheitlich im h_da Corporate Design bleiben.
Die zulässige h_da-Palette. Immer über die CSS-Variablen ansprechen, nie Hex-Werte hartkodieren.
Rolle vor Palettenfarbe: die Akzentfarbe der App liegt im Slot
--hda-brand / --hda-brand-dark / --hda-brand-rgb (aktuell h_da-Orange) —
Chrome-Elemente greifen auf diesen Slot zu, nicht direkt auf --hda-orange.
Ein Farbwechsel ist dann ein Einzeiler in hda-theme.css.
Die Akzentfarbe der App ist h_da-Orange und liegt im Slot
--hda-brand / --hda-brand-dark / --hda-brand-rgb.
Die gesamte Chrome (Buttons, Links, Nav-Aktiv, Tabs, Brand-Akzent, Form-Fokus) greift auf diesen
Slot zu, nicht direkt auf --hda-orange — ein Farbwechsel bleibt damit ein Einzeiler
in hda-theme.css.
rgba(var(--hda-brand-rgb, 233, 128, 17), X) — der Fallback
233, 128, 17 ist Pflicht (Templates sind ungecacht, die CSS nicht;
ohne Fallback „verschwindet" die Akzentfarbe bei veralteter CSS)..theme-contrast, Schwarz-Weiß) hat Vorrang:
er setzt --hda-brand* auf reines Schwarz und überschreibt damit den Akzent.Hausschrift HDA DIN (--hda-font-sans), mit System-Sans als Fallback.
Der schnelle braune Fuchs springt über den faulen Hund – 0123456789 äöüß.
Hervorgehobener Einleitungstext für Info-Boxen und Willkommensbereiche.
Jeder Text-Button trägt ein führendes Tabler-Icon, das die Aktion andeutet –
gefolgt von einem kleinen Abstand von 0.40em zum Text (app-weit über
.btn:not(.btn-icon) > .ti:first-child gesetzt, kein Leerzeichen im Markup nötig).
btn-outline-danger + data-confirm-delete — 1. Klick füllt, 2. Klick löscht.)
Ausschließlich Tabler Icons (<i class="ti ti-NAME">) — nie
Font Awesome / Bootstrap Icons. Der Icon-Webfont ist über layouts/base.html global geladen;
Standalone-Templates, die layouts/base.html nicht erweitern
(z. B. Login-/Standalone-Seiten), binden den Webfont
{% static 'tabler-icons/tabler-icons.min.css' %}
(vendored unter app/static/tabler-icons/) selbst ein.
Verbindliche Aktion→Icon-Zuordnung (bei fehlender Passung nächstliegendes Icon, Fallback
ti-chevron-right):
ti-device-floppyti-xti-trashti-pencilti-plusti-searchti-filterti-rotateti-sendti-downloadti-uploadti-arrow-leftti-arrow-rightti-loginti-logoutti-shareti-copyti-eyeti-checkti-settingsti-grid-dotsti-tableIcon-only-Controls (Schließen ×, Toolbar-Glyphen,
Theme-/Nav-Toggle) tragen kein zusätzliches Label-Icon. Text-Buttons: Icon→Text-Abstand
0.40em app-weit via .btn:not(.btn-icon) > .ti:first-child — nicht pro Button hand-tunen.
Helle bg-*-lt-Badges für Status/Kategorien (Standard), gefüllte
bg-* nur für starke Signale. Für einen reinen Punkt-Indikator (ohne Text) der
status-dot mit Tooltip.
Kleiner farbiger Punkt mit Tooltip statt Text-Badge — für dichte
Statuslisten (z. B. Abruf-Status je Hochschule). Vier Zustände; hier als Demo unter
.sg-status-dot — beim ersten echten Einsatz nach hda-theme.css ziehen.
Zwei Systeme: Tabler .avatar (Initialen oder Icon, mit
Tönungs-Klasse bg-*-lt + passender Textfarbe, Größen avatar-sm/avatar-lg)
für Nutzer/Icons; und der custom .program-avatar (Abschluss-Initiale B/M,
is-master = orange) für Studiengänge.
.avatar.program-avataris-master färbt orange (Demo: .sg-prog-avatar).
Tabler-Card mit Header und Body – Basis für Tabellen, Formulare und Detailansichten.
Begrüßungs- und Info-Boxen. Verlauf in der Marken-/Primärfarbe.
Abgesetzte Bereiche für Mitarbeitende – oranger Verlauf als Zielgruppen-Signal.
Der .card-header ist ein Flex-Container: links der .card-title,
rechts optional .card-actions (Badge, Meta-Text, Button, Icon-Button, Suchfeld).
Ein Icon im Titel steht mit <i class="ti … me-2"> davor (Titel-Abstand, nicht
die 0.40em-Button-Regel).
Überschriften-Ebene: .card-title ist eine Optik-Klasse – das
HTML-Tag folgt der Seitenhierarchie. In normalen Seiten liegt die Karte direkt unter dem
<h2>-Seitentitel → <h3 class="card-title"> (App-Standard).
Hier im Styleguide stehen die Demos unter den <h3>-Abschnitten, also eine Ebene
tiefer als <h4>.
App-Standard für Aktionen im Header: kleine Buttons (btn-sm) mit führendem Icon.
me-2 (z. B. KI-Assistent, Studiengang-Karten).card-actions text-muted small als Kontext rechts (z. B. Studiengang, CP-Summe).btn btn-primary btn-sm mit Icon – die empfohlene Header-Aktion.btn-icon ohne Label für Toolbar-Glyphen (z. B. Vollbild, Ansicht wechseln).btn-sm) für die zentrale Aktion einer Seite – sparsam einsetzen.card-actions d-flex align-items-center gap-2, input-icon + form-control-sm). Aktions-Buttons in dieser Gruppe als btn-sm, damit sie mit dem Suchfeld fluchten.nav-tabs card-header-tabs statt Titel – für gruppierte Ansichten (z. B. Admin).text-center justify-content-center – für Login-/Auth-Karten.
Basis: table table-vcenter (Zellen vertikal zentriert). In einer Karte zusätzlich
card-table (bündig an den Kartenrand), table-hover für klickbare Zeilen,
table-sm für kompakte Listen, table-bordered für Kreuztabellen/Matrizen,
table-borderless für randlose. Breite Tabellen immer in
<div class="table-responsive"> wickeln (horizontales Scrollen statt Umbruch).
Konvention: statt einer Action-Spalte die erste Spalte klickbar
machen (Link, wie Bot-Tabellen); wo Zeilen-Aktionen nötig sind, rechts Icon-Buttons
(btn-sm btn-icon) und Löschen als btn-outline-danger +
data-confirm-delete. Zahlen rechtsbündig (text-end), Status als Badge.
| Studiengang | Kürzel | Status | CP |
|---|---|---|---|
| Informatik B.Sc. |
fbi_inf_bsc | Aktiv | 210 |
| Informatik dual B.Sc. |
fbi_infdl_bsc | In Prüfung | 210 |
| Data Science M.Sc. |
fbi_ds_msc | Inaktiv | 90 |
| Name | Aktion |
|---|---|
| Prüfungsordnung BBPO21 | |
| Prüfungsordnung BBPO18 |
table-sm)| Modul | Sem. | CP |
|---|---|---|
| Programmieren 1 | 1 | 6 |
| Mathematik 1 | 1 | 8 |
| Datenbanken | 3 | 6 |
| Software Engineering | 4 | 6 |
table-bordered)| Kohorte | 1. Sem | 2. Sem | 3. Sem | 4. Sem |
|---|---|---|---|---|
| 2023W | 48 | 45 | 41 | 12 |
| 2024W | 52 | 49 | – | – |
Tabler .datagrid für Schlüssel-Wert-Metadaten in Detailansichten (statt
einer zweispaltigen Tabelle) — ein responsiv umbrechendes Raster aus datagrid-item
(datagrid-title + datagrid-content).
Tabler .progress + .progress-bar; schlank ohne Text als
.progress-sm. Farbe i. d. R. bg-primary, für Risiko/Ampel dynamisch
bg-{{ risk_color }} (green/orange/red).
Eingesetzt für ECTS-Fortschritt und Erfolgsquoten.
Schlichte Zeilenlisten (Noten, Zuordnungen) — meist
list-group-flush + d-flex justify-content-between; klickbare
Zeilen als list-group-item-action.
Bootstrap-Accordion (data-bs-toggle="collapse" +
data-bs-parent) für aufklappbare Gruppen, z. B. BBPO-Versionen.
layouts/base.html liefert das Grundgerüst: den page-header (mit
<h2 class="page-title"> aus der Context-Variable page_title) und die
page-body. Content-Seiten füllen nur Blöcke — keinen eigenen Seitenkopf bauen.
{% extends "layouts/base.html" %}
{% block title %}…{% endblock %} {# Browser-Tab #}
{% block page_subtitle %}…{% endblock %} {# <div class="text-muted mt-1"> unter dem Titel #}
{% block page_actions %}…{% endblock %} {# rechts oben, meist <div class="btn-list"> #}
{% block content %}…{% endblock %}
{% block styles %}…{% endblock %} · {% block scripts %}…{% endblock %}
Breadcrumbs kommen datengetrieben aus der Context-Liste
breadcrumbs ([{label, url}]) und rendern automatisch im Header als
breadcrumb breadcrumb-arrows. Statische Variante bei Bedarf:
Auf Seitenebene nav nav-tabs + tab-content/tab-pane,
umgeschaltet über data-bs-toggle="tab" (client-seitig, z. B. Studierenden-Detail). Für
server-nachgeladene Ansichten stattdessen nav-tabs card-header-tabs + HTMX
(hx-get → #target, aktiver Tab via active_tab).
nav-pills wird nicht verwendet.
btn-group role="group" mit aktivem Zustand über .active
(per JS gesetzt, data-value/data-view). Für den Wechsel Kacheln↔Tabelle die
Standard-Icons ti-grid-dots / ti-table.
Bootstrap-Dropdown (data-bs-toggle="dropdown"). Als Aktions-Menü ein
btn-icon mit ti-dots-vertical; Menüpunkte tragen ein führendes Icon mit
me-2, Trenner via dropdown-divider, rechtsbündig mit
dropdown-menu-end.
Tabler pagination mit Chevron-Icons; aktive Seite als
page-item active mit <span class="page-link">. Kompakt via
pagination-sm, im card-footer rechtsbündig.
Standard: modal modal-blur fade + modal-dialog modal-dialog-centered,
Größe modal-sm/modal-lg/modal-xl, bei langem Inhalt
modal-dialog-scrollable. Der Header trägt modal-title + btn-close
(Icon-only, kein Label-Icon), die Footer-Buttons tragen wie üblich ein führendes Icon.
data-bs-toggle="modal" data-bs-target="#…"
Für „Wirklich löschen?"-Rückfragen ist die
Inline-Bestätigung data-confirm-delete (arm-then-confirm, s. u.)
der Standard — kein eigener Dialog nötig.
Toast = flüchtige Rückmeldung (rechts unten, auto-dismiss). Django-
messages werden app-weit automatisch als Toast ausgegeben (layouts/base.html),
nicht als Alert-Box; programmatisch window.hdaToast("…") /
hdaToast("…","error") (s. Interaktive Bausteine). Alert = persistenter
Hinweis im Seitenfluss; Varianten alert-success/-danger/-warning/-info/-secondary,
optional alert-dismissible + btn-close, Icon als inline
<i class="ti …">.
Je nach Zweck einen der beiden Mechanismen wählen:
.hintCSS-only, für Fachbegriffe im Fließtext: unterstrichenes Wort, Hover
zeigt eine dunkle Box. <span class="hint" data-tooltip="…">Begriff</span>
(s. Interaktive Bausteine). Kein JS nötig.
Der ECTS-Wert …
Für Icon-Buttons/Steuerelemente: data-bs-toggle="tooltip" +
data-bs-title="…". Wird global in base.html initialisiert (auch nach
HTMX-Swaps). Für einfache Fälle genügt das native title="…".
Visualisierungen (Notentrend, Kohorten) nutzen ApexCharts, global in
base.html geladen. In die Marken-Palette einfärben — Primär-Serie h_da-Orange
#E98011 (bzw. Anthrazit #231F20); keine Apex-Default-Farben. Der Container ist ein
leeres <div id="…">, die Config kommt im
{% block scripts %}.
Die Begrüßungsbox oben auf den Hauptseiten (Dashboard, Study-Monitoring, Studiengänge …):
Verlaufsfläche in Markenfarbe, links Icon + Titel + Lead, rechts eine unDraw-Illustration
(portal-welcome-art, auf schmalen Breiten ausgeblendet), darunter optional bis zu drei
portal-step-Kacheln als Einstiegs-Links. Für Mitarbeitende die kompakte Variante
portal-welcome--compact. Demo unter .sg-hero/.sg-step.
Mitbewerber-Analyse für die Informatik-Studiengänge: vergleichbare Angebote anderer Hochschulen erfassen, gegenüberstellen und auswerten.
.hero-context)App-weit einheitliche „aktuell selektiert / zugeordnet"-Zeile unter dem Lead:
.hero-context-label (Icon + Text) + Chips. Chips read-only als
<span class="hero-context-chip"> oder navigierbar als
<a class="hero-context-chip">; für Pfade Chevron-Trenner
.hero-context-sep. Farben laufen über die Marken-Variablen und kippen im
Mitarbeitenden-Theme automatisch auf Orange. Genutzt in Dashboard, KI-Assistent,
Kohortenanalyse und Study-Monitoring — nicht pro Seite nachbauen.
Spot-Illustrationen von unDraw
(MIT-Lizenz, attributionsfrei). Diese Galerie wird automatisch aus dem Ordner erzeugt –
neue undraw_*.svg erscheinen hier von selbst.
So bindest du eine neue Illustration ein:
#6c63ff → h_da-Orange
#E98011 ersetzen —
sed 's/#6c63ff/#E98011/gI' rohdatei.svg > undraw_<slug>_hda_orange.svg.<app>/static/**/img/undraw_*.svg, im Projekt app/static/hda/img/) und den
Original-Slug im Dateinamen behalten (Nachvollziehbarkeit, z. B.
undraw_interview_hda.svg ← interview_yz52).{% static 'hda/img/undraw_….svg' %} referenzieren —
niemals cdn.undraw.co hotlinken (CDN-Pfad ist
cdn.undraw.co/illustration/<slug>.svg, Singular illustration).App-weite JS-Utilities aus static/hda/js/, global in
layouts/base.html geladen — nicht pro Seite nachbauen.
Beliebiges Element mit data-copy="…" (optional
data-copy-message); Inline-Trigger als btn-copy. Toasts programmatisch:
window.hdaToast("…") bzw. hdaToast("…", "error").
Utility: static/hda/js/clipboard.js (self-injiziert sein CSS).
.inline-edit +
data-ie-url/data-ie-field/data-ie-value. Hover zeigt einen
orange gestrichelten Indikator; Klick öffnet den Editor (bei viel Text ein Textarea in
Originalgröße), Enter/Blur speichert (Endpoint gibt JSON mit HTTP 200 zurück), Esc bricht ab.
Beim Hover erscheint am Zeilenende ein KI-Icon zum Umformulieren
(Presets Rechtschreibung/Sachlicher/Freundlicher/Du/Sie/Deutsch/Englisch + eigener Prompt) —
es postet nach /ai/rewrite/, in diesem Projekt noch nicht implementiert.
Für Icon-Felder: data-ie-render="icon" öffnet den Icon-Picker.
Utility: static/hda/js/inline-edit.js.
Beispiel (Demo, speichert nichts): Klick mich zum Bearbeiten
Ruhezustand btn-outline-danger +
data-confirm-delete (optional data-confirm-label). 1. Klick füllt zu
btn-danger und zeigt die Bestätigung, erst der 2. Klick führt aus; Klick daneben / Esc /
Timeout entschärfen. Funktioniert für Links, Form-Submit und eigene JS-Click-Handler
(static/hda/js/confirm-delete.js).
data-table-filter)App-weite JS-Utility (static/hda/js/table-filter.js):
ein Suchfeld filtert clientseitig die Zeilen einer Tabelle über ihren Text. Opt-in am
<input data-table-filter="#tbody">; optional data-filter-count
(Element für den Trefferzähler) und data-filter-label. Gefiltert werden Zeilen mit
data-row; als Suchtext dient data-search (falls gesetzt, z. B. voller
Pfad für hierarchische Listen), sonst der Zeilentext. data-no-match blendet eine
„Kein Treffer"-Zeile ein. Läuft auch für per htmx nachgeladene Inhalte. Toolbar-Standard
(vgl. Card-Header „Titel + Suchfeld"): input-icon + form-control-sm, Count-Badge,
Add-Button rechts als btn-sm — app-weit einheitlich (z. B. alle Administration-Tabs).
| Studiengang | Fachbereich |
|---|---|
| Informatik B.Sc. | FB Informatik |
| Data Science M.Sc. | FB Mathematik und Naturwissenschaften |
| Mechatronik B.Eng. | FB Maschinenbau |
| Kein Treffer. | |
Auf (auch ausgeloggt erreichbaren) Seiten stehen E-Mail-Adressen
nicht im Klartext im HTML, sondern nur kodiert (data-eml);
static/hda/js/email-protect.js macht daraus beim Laden automatisch einen
klickbaren mailto:-Link — ohne dass Nutzer*innen etwas tun. Verwendung:
{% load email_protect %} dann Tag
{% protect_email "name@example.com" %} (Link) oder Filter
{{ mail|obfuscate_email }} für eigene Attribute
(im JS via window.hdaDecodeEmail(value) entschlüsseln). Läuft auch nach htmx-Swaps.
Beispiel (Quelltext enthält keinen Klartext, JS macht daraus den Link): E-Mail
.hint)App-weite CSS-Utility (static/hda/css/hda-theme.css):
ein unterstrichener Begriff, der beim Hovern eine gestylte dunkle Box mit Erklärtext zeigt.
Markup: <span class="hint" data-tooltip="Erklärung">Begriff</span>.
(.org-unit-tooltip ist ein Alt-Alias mit gleicher Optik.)
Beispiel: Der ECTS-Wert ergibt sich aus den bestandenen Modulen.
Container (z. B. <tbody>) bekommt data-sortable
+ data-sortable-url (POST-Ziel), jede Zeile data-sortable-id="<pk>" und einen
Greifpunkt .sortable-handle mit ti-grip-vertical (nur dort startet das Ziehen).
Beim Loslassen wird order[]=<id>… gepostet und hdaToast meldet das Ergebnis.
Alle Handler sind delegiert — auch dynamisch (z. B. per AJAX) eingefügte Zeilen sind sofort sortierbar.
Utility: static/hda/js/sortable-rows.js.
| Erste Zeile | |
| Zweite Zeile | |
| Dritte Zeile |
Jedes <select class="js-tomselect"> wird von
select-widgets.js auto-initialisiert (nach tom-select.complete.min.js) —
Tabler-nativ, select2/jQuery projektweit entfernt. Konfiguration rein über
Attribute; nichts pro Seite verdrahten.
Single-Selects erhalten automatisch ein „×" zum Abwählen
(clear_button-Plugin) — sichtbar nur, wenn etwas gewählt ist;
Mehrfachauswahl ein remove_button-„×" je Chip.
js-tomselect + data-placeholder (leere erste Option für den Platzhalter); nach der Wahl erscheint das × zum Abwählen
multiple → remove_button-Plugin automatisch (× je Eintrag)
data-ts-create erlaubt neue Einträge (data-ts-create="email" nur, wenn der Text ein „@" enthält)
Optionen serverseitig laden statt statisch: data-ts-url
zeigt auf einen Endpunkt (Query-Param ?q=), Antwortformat
{"results":[{"id","text"}]}. Programmatisch alternativ
window.hdaSelectRemoteLoad(url) als TomSelect-load-Funktion.
<select class="js-tomselect"
data-ts-url="/portal/api/users?q="
data-placeholder="Person suchen …"></select>
Zwei-Spalten-Widget für Mehrfachzuordnungen (z. B. Studiengänge → Benutzer):
links die verfügbaren, rechts die zugeordneten Einträge; verschoben
wird per Doppelklick auf einen Eintrag oder über die Pfeil-Buttons (markierte / alle).
Progressive Enhancement über ein <select multiple> — das native Select bleibt versteckt
im DOM, sodass der Form-Submit unverändert läuft. Opt-in per data-dual-list; global geladen
(hda/js/dual-list.js), nichts pro Seite verdrahten. Für einfache, kurze
Mehrfachauswahl bleibt TomSelect (oben) die erste Wahl; Dual-List lohnt bei längeren Listen und wenn die
Trennung „verfügbar ↔ zugeordnet" explizit sichtbar sein soll.
data-dual-list-search="0" blendet sie aus).
<select class="form-select" name="programs" multiple data-dual-list
data-dual-list-available="Verfügbar" data-dual-list-selected="Zugeordnet">
<option value="if-b" selected>Informatik B.Sc.</option>
…
</select>