Formulare
Native Elemente werden fertig gestylt (helpers/forms.css), keine JS-Nachbauten. Lebendes Beispiel: demo/forms.html.
Basis-Felder
| Klasse | Zweck |
|---|---|
.ncss-field | Label+Input+Hint als vertikale Gruppe |
.ncss-label / --required | Feld-Label, --required ergänzt ein rotes * |
.ncss-input / .ncss-select / .ncss-textarea | Einheitlicher Look, inkl. :user-invalid-Fehlerzustand |
.ncss-field-hint / -error | Hilfetext / Fehlertext unter einem Feld |
Weitere Feldtypen (cross-browser gestylt)
| Typ | Besonderheit |
|---|---|
type="file" | Größter Cross-Browser-Unterschied aller Feldtypen - ::file-selector-button (+ ::-webkit-file-upload-button für ältere Safari) bringt den Browser-Button auf .ncss-btn--secondary-Optik |
type="color" | Swatch-Fläche per ::-webkit-color-swatch(-wrapper)/::-moz-color-swatch an ncss-Radius angeglichen, feste Höhe passend zu den anderen Feldern |
type="date/time/datetime-local/month/week" | Kalender-/Uhr-Icon folgt automatisch dem Theme (color-scheme), keine eigene Anpassung nötig |
type="search" | appearance: none nötig, sonst rundet Safari das Feld eigenständig komplett ab |
type="number" | Native Spinner-Pfeile bewusst NICHT entfernt - echte Funktionalität, kein Grund sie zu kappen |
<input list> + <datalist> | Nativer Combobox - editierbares Textfeld MIT Auswahlliste (anders als <select>, das nur Auswahl ohne freie Eingabe erlaubt). .ncss-input greift ohne Sonderregel (generisch auf jedes <input>) |
<input class="ncss-input" list="staedte" placeholder="Wählen oder eingeben…">
<datalist id="staedte">
<option value="Berlin"></option>
<option value="Hamburg"></option>
</datalist>
Gestylte Dropdowns (opt-in)
Die native Vorschlagsliste eines <datalist> UND das Options-Popup eines <select> sind in keinem Browser per CSS stylebar (Stand 2026, geprüft) - für <select> gibt es mit appearance: base-select mittlerweile einen CSS-Weg, den ncss aktuell nur in Chrome/Edge 135+ aktiviert (siehe Komponenten-Referenz für die Begründung, warum Safari bewusst ausgenommen ist), für <datalist> gibt es gar keinen.
js/combobox.js (opt-in) ersetzt in beiden Fällen die native Popup-Optik durch eine echte, gestylte Dropdown-Liste - Original-Markup unverändert (<input list>/<datalist> bzw. <select>/<option>), volle native Funktion bleibt ohne JS bestehen (progressive enhancement, kein Funktionsverlust). Bei <select> greift es genau dort, wo ncss appearance: base-select NICHT aktiviert (Firefox UND aktuell auch Safari) - wo die native Technik bereits ein gestyltes Popup liefert (Chrome/Edge), tut das Script nichts.
<script src="js/combobox.js" defer></script>
<button><selectedcontent> als erstes Kind IMMER angeben, auch bei ganz normalem Text-Select - laut Spezifikation optional (der Browser erzeugt sonst selbst einen passenden Default-Button), macht das Verhalten aber über alle Browser hinweg konsistent und vorhersehbar.
<select class="ncss-select">
<button type="button">
<selectedcontent></selectedcontent>
</button>
<option>Option A</option>
<option>Option B</option>
</select>
Reicher <option>-Inhalt (nativ nur mit appearance: base-select, sonst per js/combobox.js nachgebildet): <option> darf seit der "Customizable Select"-Erweiterung beliebige Kind-Elemente enthalten (Icon+Label statt nur Text), derselbe <button><selectedcontent> steuert dann zusätzlich die Anzeige im geschlossenen Zustand. "The Golden Rule of Customizable Select": Text bleibt Pflicht (sichtbar oder per .ncss-visually-hidden), Icons sind eine Ergänzung, nie ein Ersatz.
<select class="ncss-select">
<button type="button">
<selectedcontent></selectedcontent>
</button>
<option value="apfel">
<span aria-hidden="true">🍎</span>
<span>Apfel</span>
</option>
</select>
js/combobox.js repliziert denselben reichen Inhalt für den JS-Fallback-Pfad (Firefox und aktuell auch Safari) - <option>-Kind-Elemente werden 1:1 in die custom Dropdown-Liste geklont statt nur der zusammengequetschte Text. Optisch identisches Ergebnis, per echtem Test in Chromium (nativ) sowie WebKit und Firefox (beide JS-Fallback) bestätigt.
Live-Suche (opt-in)
components/livesearch.css + js/livesearch.js zeigen schon während der Eingabe Treffer an - gruppiert (z.B. „Seiten“, „Personen“), mit markiertem Suchbegriff. Anders als js/combobox.js, das eine vorhandene <datalist> filtert, kommen die Treffer hier per fetch() vom Server. Muster: WAI-ARIA Combobox mit Listbox - der Fokus bleibt im Eingabefeld, aria-activedescendant zeigt auf die gewählte Option, eine Statusmeldung nennt die Trefferzahl. Ohne JavaScript bleibt es ein ganz normales Suchformular (Absenden führt zur Ergebnisseite).
<form class="ncss-livesearch" action="/suche/" role="search" data-ncss-livesearch="/api/suche">
<label class="ncss-visually-hidden" for="q">Suche</label>
<input id="q" class="ncss-input" type="search" name="q">
<div class="ncss-livesearch-results" hidden></div>
</form>
<script src="js/livesearch.js" defer></script>
Antwort-Format des Endpoints (GET /api/suche?q=…, bereits gefiltert):
{
"groups": [
{ "label": "Seiten", "items": [ { "title": "Kontakt", "url": "/kontakt/", "text": "Anfahrt und Öffnungszeiten" } ] }
],
"all": "/suche/?q=..."
}
data-ncss-livesearch | Endpoint (Pflicht) |
data-param | Name des Suchparameters, Default q |
data-min / data-delay | Mindestlänge (Default 2) und Wartezeit nach dem letzten Tastendruck (Default 180 ms); ältere Anfragen werden per AbortController abgebrochen, Antworten pro Suchbegriff zwischengespeichert |
data-mode="client" | Endpoint ist eine STATISCHE JSON-Datei im selben Format (ungefiltert): einmal laden, im Browser filtern - für kleine Datenmengen ohne Server-Logik |
data-text-empty/-all/-status | Texte überschreiben (Platzhalter {q}, {n}); Default Deutsch/Englisch nach <html lang> |
Kombinierbar mit .ncss-search (wachsendes Suchfeld) und .ncss-input-group. Innerhalb eines <dialog> schließt Esc zuerst nur die Trefferliste, nicht den Dialog. Danach gilt wieder das Browser-Verhalten: ein type="search"-Feld leert beim nächsten Esc zuerst seinen Inhalt, erst das folgende Esc schließt den Dialog. Live: Demo.
Checkbox, Radio & Switch
.ncss-choice | Checkbox/Radio-Zeile - native Elemente, nur accent-color + Ausrichtung |
.ncss-switch | Toggle-Switch aus <input type="checkbox">, per appearance:none neu gezeichnet, kein JS. role="switch" im Markup ergänzen |
.ncss-range | Vereinheitlichtes <input type="range">, Track/Thumb angeglichen |
Gruppierung & Rückmeldung
fieldset / legend | Ohne eigene Klasse zu Ende gestylt (wie <blockquote>) - ersetzt den nativen 3D-Rahmen |
progress / meter | accent-color für die Grundfarbe. meter behält bewusst seine eigene Grün/Gelb/Rot-Bewertungslogik statt der Markenfarbe |
output | Hervorgehobene Darstellung für ein berechnetes Ergebnis |
Input-Group
components/input-group.css - mehrere Formular-Steuerelemente visuell zu EINER Einheit verschmolzen (geteilte Rahmenlinie statt Lücke, nur die äußeren Ecken rund). Deckt zwei Fälle ab: Feld + Button (Suchleiste) und mehrere Felder nebeneinander (z.B. Vorname/Nachname). Bewusst NICHT "Form-Group" genannt - leicht zu verwechseln mit der bereits bestehenden <fieldset>/<legend>-Gruppierung (Semantik statt reiner Optik).
<label class="ncss-visually-hidden" for="search">Suchbegriff</label>
<div class="ncss-input-group">
<input class="ncss-input" type="search" id="search" placeholder="Suchbegriff…">
<button type="submit" class="ncss-btn ncss-btn--primary">Suchen</button>
</div>
Barrierefreiheit: placeholder ist KEIN Ersatz für ein Label - verschwindet bei Eingabe, wird nicht von jeder Screenreader-Software als Feldname angekündigt. Jedes Feld braucht ein echtes <label>, bei Bedarf per .ncss-visually-hidden versteckt - als Geschwister-Element VOR der .ncss-input-group, nicht als Kind hinein (sonst zählt es fälschlich als :first-child und die Eckenrundung landet auf dem unsichtbaren Label). Bei mehreren direkt verschmolzenen Feldern ohne Platz für ein Sibling-Label je Feld (z.B. Vorname/Nachname) ist aria-label direkt am <input> die richtige Technik, ohne die Kind-Struktur der Gruppe zu verändern:
<div class="ncss-input-group">
<input class="ncss-input" aria-label="Vorname" placeholder="Vorname">
<input class="ncss-input" aria-label="Nachname" placeholder="Nachname">
</div>
Deckt aktuell .ncss-input/-textarea/.ncss-btn als direkte Kinder ab - .ncss-select-wrapper (noch) nicht (der sichtbare Rahmen sitzt am inneren <select>, nicht am Wrapper selbst).
Buttons
.ncss-btn + --primary/--secondary/--danger. Jede Variante hat eine ECHTE :active-Rückmeldung (nicht nur :hover) - siehe Touch & mobile Viewports für die Begründung.
<button class="ncss-btn ncss-btn--primary">Absenden</button>