Themes¶
Themes sind Daten: Paletten für hellen und dunklen Modus, Typografie, Eckenradius, Breite der Navigation und Höhe der App-Leiste, Schatten, Logo, eigenes CSS und die Optionen der App. Einundzwanzig Themes sind eingebaut (siehe Eingebaute Themes); sie unterscheiden sich in Schrift, Radius, Breite und Verhalten der Navigation, nicht nur in den Farben.

- Administration > Themes (
Themes.Manage) zeigt je Theme eine Kachel: helle und dunkle Farben, Name, Veröffentlichungsstatus, Bearbeiten, Als Standard festlegen und Löschen (nur eigene Themes, nach Rückfrage). Bearbeiten und Neues Theme öffnen einenMudExThemeEdit<CoworkeeTheme>mit Live-Vorschau; ein neues Theme startet von einem der vorhandenen. Eingebaute Themes bleiben unverändert, Speichern legt eine Kopie an. - Alle Benutzer können die eingebauten und die veröffentlichten Themes wählen; der Standard des Mandanten gilt, bis ein Benutzer in den Kontoeinstellungen oder über die Palette in der App-Leiste ein anderes Theme (und hellen, dunklen oder System-Modus) wählt.
CoworkeeThemeerbt vonMudThemeund ergänzt:ShowLogoInNav,ShowLogoInAppBar,ShowNavFilter,CanPinNav,CanChangeNavExpandMode,NavSingleExpand(bis der Benutzer wählt),Dense,StripedTables,IsPublished,Logo,CustomCss. Alle sind Felder im Editor.CustomCssist ein Stylesheet für die ganze App (nach dem Theme gerendert) und wird mit dem CssRulesEditor bearbeitet: im Modus Regeln als Liste von Regeln (je ein Selektor und ein Style-Editor, mit Verschieben und Löschen) und Regel hinzufügen; im Modus Roh als ganzes Stylesheet in einem Monaco-Code-Editor (auch im Vollbild). At-Regeln, Kommentare und verschachteltes CSS bleiben in der Regelliste Rohblöcke. Der Modus wird pro Browser gemerkt; Speichern aus der Regelliste normalisiert das CSS (eine Deklaration pro Zeile), ein leeres Stylesheet wird als nichts gespeichert. Den CssRulesEditor kann man auch allein verwenden (Value, ValueChanged, Label, HelperText, ReadOnly).Dense(standardmäßig an) macht die ganze Oberfläche kompakt: Tabellen, Listen, Menüs, Baumansichten, Hinweise, Ausklappbereiche und Checkboxen werdenDensedargestellt, Eingabefelder und Auswahllisten mitMargin.Dense, Chips mitSize.Small. Formulare (MudExObjectEdit) und das Navigationsmenü behalten ihre normale Größe. Themes, die mit der früheren OptionDenseTablesgespeichert wurden, behalten ihren Wert.- Die Komponenten der Bibliothek erben von
CoworkeeComponentBase, das dieDensitydes Themes als Cascading Value erhält (Density.Dense,Density.Margin,Density.Size). Eigene Komponenten können das genauso nutzen:@inherits CoworkeeComponentBaseoder[CascadingParameter] Density Density { get; set; } = Density.Default;. Eine eigeneIObjectMetaConfiguration<T>eines Modells ersetzt die kompakte Formular-Konfiguration für dieses Modell. Logoist SVG-Markup oder eine Bild-Data-URL (PNG, JPEG, WebP oder SVG, höchstens 512 KB; SVG mit Skripten, Event-Handlern oder eingebetteten Dokumenten wird abgelehnt). Der Editor zeigt es mitThemeLogoEdit: Vorschau, Logo wählen (Upload, die konfigurierten Cloud-Laufwerke oder Aus Dateien wählen mit den Dateien der App) und App-Logo verwenden. Rasterbilder über 512 px werden vorher im Browser verkleinert. Das Logo erscheint in der App-Leiste, der Navigation, im Setup, im Über-Dialog und auf den Kontoseiten und ist, solange es gesetzt ist, das Favicon.- Ohne Logo im Theme erscheint das Logo der App:
options.AppLogo = "logo.svg"inAddCoworkeeClient(Coworkee:Auth:LogoUrlfür die Kontoseiten). - Auf dem Server macht
ThemeLogo.TryRead(theme.Logo, out var bytes, out var contentType)(Coworkee.Contracts.Theming) aus beiden Formen Bytes und Content-Type, etwa um das Logo in eine Datei zu schreiben. - Die Spalte
LogoSvgder Theme-Tabelle heißt jetztLogo: Eine bestehende App braucht eine Migration (EF Core benennt die Spalte um, wenn du die erzeugte Migration aufRenameColumnänderst). - Das Setup bietet das Theme als eigenen Schritt an.
- Die Kontoseiten des Auth-Servers übernehmen Farben, Schrift und Logo des Standard-Themes. Layout und Medien sind eine Einstellung für die ganze Installation: die Karte Anmeldeseite oben auf Administration > Themes, siehe Anmeldeseiten und Designs.
- Die Felder des Theme-Editors kommen aus MudEx-Metadaten für Objektformulare. Die Bibliothek registriert
CoworkeeThemeEditMetaalsIObjectMetaConfiguration<CoworkeeTheme>(die FelderLogoundCustomCss); registriere nachAddCoworkeeClienteine eigene, um Felder anders darzustellen (sie ersetzt die der Bibliothek):
builder.Services.AddScoped<IObjectMetaConfiguration<CoworkeeTheme>, MyThemeEditMeta>();
public sealed class MyThemeEditMeta : IObjectMetaConfiguration<CoworkeeTheme>
{
public Task ConfigureAsync(ObjectEditMeta<CoworkeeTheme> meta)
{
meta.Property(t => t.Logo).WithLabel("Firmenlogo");
return Task.CompletedTask;
}
}
ThemeService im Client wendet ein Theme zur Laufzeit an (Apply, Preview, SetMode).
Eingebaute Themes¶
Jedes hat eine helle und eine dunkle Palette. Die Namen deuten nur an, was sie inspiriert hat, und keins bringt ein Logo mit; GitHub ist nach der Seite benannt, der es folgt. Sie unterscheiden sich auch darin, wo das Logo sitzt: im Menü, im Header, in beiden oder nirgends (ShowLogoInNav, ShowLogoInAppBar); LinkHub bringt eigenes CSS für seine Pillen-Schaltflächen mit. Die neueren (ab Octo) erfüllen den Kontrast nach WCAG AA für Text, Schaltflächen, App-Leiste und Navigation (ThemeSeedTests).
| Theme | Aussehen | Überschriften / Text | Radius |
|---|---|---|---|
| Ocean (Standard) | blaue App-Leiste, Cyan als Akzent | Segoe UI | 14 px |
| Coworkee | warmes Orange auf Papier | Inter | 6 px |
| Classic | MudBlazor-Lila | Roboto | 4 px |
| Forest | Grün, Bernstein als Akzent | Georgia / Segoe UI | 4 px |
| Sunset | Pink, luftig | Trebuchet MS | 20 px |
| Midnight | Indigo, Violett als Akzent | Cascadia Code / Inter | 8 px |
| High Contrast | Schwarz-Weiß (dunkel: Gelb auf Schwarz), ohne Schatten | Arial | 0 |
| Octo | neutrale Grautöne und Blau, wie bei einer Code-Hosting-Seite | Systemschrift | 6 px |
| Code | Editor-Look: kompakt, graue Titelleiste, Blau | Segoe UI, kompakt | 2 px |
| Arctic | kühle Polarblautöne und Frost, dunkle App-Leiste | Inter | 10 px |
| Vampire | Lila und Pink auf nächtlichem Hintergrund (tagsüber Creme) | Inter | 12 px |
| Solar | warmes Creme und tiefes Petrol, Bernstein als Akzent | Georgia / Segoe UI | 4 px |
| Atom | sanfte Editor-Grautöne, Blau und Violett | Cascadia Code / Inter, kompakt | 6 px |
| Paper | schlichtes weißes Papier, flach (ohne Schatten) | Georgia / Inter | 4 px |
| Aubergine | auberginefarbene App-Leiste (im dunklen Modus auch die Navigation), grüne Akzente | Lato | 8 px |
| GitHub | dunkler Header, weiße Seiten, blaue Links, wie bei GitHub; Logo nur im Header | Systemschrift | 6 px |
| LinkHub | Link-Seiten-Look: Limette und Violett, Pillen-Schaltflächen, luftig; Logo nur über dem Menü | Lato | 24 px |
| Terminal | Grün auf Schwarz wie eine Shell, Monospace, eckig; kein Logo, flach | Cascadia Code, kompakt | 0 |
| Slate | dunkle Seitenleiste neben hellen Seiten, Indigo; Logo nur über dem Menü | Inter | 8 px |
| Ember | Bernstein auf Anthrazit, dunkler Header; Logo nur im Header | Inter / Segoe UI | 6 px |
| Mint | frisches Mint und Petrol, luftig; Logo nur im Header | Trebuchet MS / Lato | 12 px |


Für eine neue App wählst du eins mit coworkee new --theme Octo (oder theme in der Spec), in Coworkee Studio auf der Seite Branding oder später als Standard der Organisation unter Administration > Themes.
Ladebildschirm¶
Während die WebAssembly-App lädt, zeigt der Web-Host einen Ladebildschirm in den Farben und mit dem Logo des Standard-Themes der Organisation (vor der Anmeldung das der Installation). Die App.razor der Vorlage setzt ihn an den Anfang des Body:
- Der Server rendert ihn (statisch, aus
GET /api/v1/themes/loading-screen, das jeder aufrufen darf; pro Organisation 30 Sekunden zwischengespeichert, höchstens 2 Sekunden Wartezeit, neutrale Farben, wenn die API nicht antwortet), daher hat schon das erste Bild die richtigen Farben. Das Layout merkt sich Farbmodus, angewendetes Theme und Sprache des Benutzers im Cookiecoworkee-loading(mode,theme,culture): Hell oder dunkel folgt ihm, sonst der Systemeinstellung. Angemeldete Besucher erhalten das Theme, das sie gewählt haben (?themeId=, von der API geprüft: nur ein eingebautes oder veröffentlichtes Theme der eigenen Organisation, für alle dort dieselbe Antwort; alles andere und jeder anonyme Besucher erhält das Standard-Theme). Die Texte kommen in der Sprache des Cookies, sonst des Browsers (Accept-Language), abgebildet auf eine der aktiven Sprachen der App (/api/v1/localization/languages: ausde-ATwirdde, eine nicht aktive Sprache wird Englisch), aus/api/v1/localization/texts/{culture}. Logo und Skript sind eigene, zwischenspeicherbare Anfragen (/bff/loading-screen/logo?v={theme}-{revision},/bff/loading-screen/script.js?v={hash}; kein Inline-Skript und kein Event-Handler, daher genügt eine Content-Security-Policy mitscript-src 'self'), und die Seite geht mitCache-Control: no-cache, no-storehinaus, weil sie Theme und Sprache des Besuchers enthält. - Der Ring um das Logo zeigt den echten Download-Fortschritt von Blazor (
--blazor-load-percentage); solange es keinen gibt (Laufzeit aus dem Cache, Start), dreht er sich.CoworkeeLayoutundSetupLayoutblenden ihn aus, sobald das Theme angewendet ist (sie beanspruchen den Bildschirm mit einem verborgenen Element[data-coworkee-loading-claim]). Ohne diesen Anspruch, etwa bei einer App mit eigenem Layout oder einer Seite ohneCoworkeeLayout, verschwindet der Bildschirm, sobald die App ihren ersten Inhalt gerendert hat; ein eigenes Layout, das zuerst ein Theme anwendet, rendert das Element selbst und ruftwindow.coworkeeLoadingScreen.hide()auf (30 Sekunden nach dem Download verschwindet ein beanspruchter Bildschirm von selbst). - Schlägt der Start fehl (Blazor zeigt seine Fehleranzeige nach einem Download- oder Startfehler, einer unbehandelten Ausnahme oder einem Circuit-Fehler; ein Framework-Skript lädt nicht; ein Fehler oder abgelehntes Promise des Frameworks bleibt unbehandelt), wird der Bildschirm sofort zu einer Meldung im selben Design: Die App konnte nicht starten., ein Hinweis, Neu laden und Details mit dem Fehler (dem letzten Konsolenfehler). Blazors eigenes
#blazor-error-uibleibt verborgen, solange die Meldung steht; der Code einer App kannwindow.coworkeeLoadingScreen.fail(detail)selbst aufrufen. - Hintergrund: die Einstellung Ladebildschirm auf der Karte Anmeldeseite (Administration > Themes): Animierter Hintergrund (Standard, sanft bewegte Formen in den Farben des Themes), Bild oder Video der Anmeldeseite (unter einem Schleier in den Farben des Themes; ohne Medium oder wenn es nicht lädt, erscheint die Animation) oder Schlichter Hintergrund. Sie wird mit dem Anmeldedesign gespeichert (
Coworkee:Auth:Design:LoadingScreen,Animated,SignInMedia,Plain); Aspire setzt sie mitoptions.SignIn.LoadingScreen. - Mit
prefers-reduced-motionbewegt sich nichts: keine Formen, kein Pulsieren, ein stehendes Videobild. Logoerscheint, wenn das Theme keines hat.- Apps, die vor dieser Version erstellt wurden, ergänzen die Zeile oben in
MyApp.Web/App.razor.
Standard-Theme und Themes der App¶
ThemingOptions bestimmt das Standard-Theme der Installation und ergänzt Themes der App. Ein App-Theme ist ein eingebautes Theme mit anderen Farben; es wird wie die eingebauten angelegt und gepflegt (in der App schreibgeschützt, im Setup angeboten). Ein geändertes Primary oder Secondary bekommt eine lesbare Kontrastfarbe für Text, sofern die Palette keine nennt.
context.Services.Configure<ThemingOptions>(themes =>
{
themes.DefaultTheme = "Shop";
themes.Themes.Add(new AppTheme("Shop", "Ocean", new Dictionary<string, string> { ["Primary"] = "#0a7d5a", ["Secondary"] = "#ff8f00" }));
});
Ein unbekannter Name in DefaultTheme lässt Ocean als Standard. Ein App-Theme mit dem Namen eines eingebauten oder eines anderen App-Themes, oder auf Basis eines unbekannten Themes, hält den Host beim Start mit einer Meldung an. Der Standard gilt, bis eine Organisation ein eigenes Theme wählt (Setup oder Als Standard). coworkee new --theme Forest --primary-color "#0a7d5a" schreibt das für dich.
Keycloak-Anmeldeseite¶
Mit Coworkee:Keycloak:ThemeDirectory (dem Themes-Ordner von Keycloak) und ThemeName hält die API ein Keycloak-Login-Theme im Design der App: die Farben des Standard-Themes der System-Organisation für hellen und dunklen Modus, sein Logo (ein Rasterbild als Datei neben dem Stylesheet, SVG darin eingebettet; ohne Logo LogoUrl), AppName über dem Formular sowie Layout und Medien aus dem Design der Anmeldeseite. Relative Pfade (/media/welcome.mp4) werden gegen BaseUrl aufgelöst. Die API schreibt das Theme beim Start und eine Sekunde nachdem eine Theme-Änderung gespeichert oder das Anmelde-Design gespeichert wurde; jede Datei entsteht neben der alten und wird dann darüber verschoben, Keycloak liest also nie eine halbe Datei. Der Aspire-AppHost füllt diese Einstellungen und mountet den Ordner (siehe Aspire-AppHost).
Die Karte Keycloak-Anmeldeseite unter Administration > Themes zeigt, wann das Theme zuletzt geschrieben wurde (oder warum es scheiterte), und schreibt es mit Nach Keycloak übernehmen sofort; sie erscheint nur, wenn die API einen Theme-Ordner hat, und nur für die System-Organisation (andere Organisationen bekommen Configured: false und beim Übernehmen 403). Schreibfehler stehen im Log der API; die Karte meldet nur, dass das Schreiben fehlschlug. Die Endpunkte sind GET /api/v1/themes/keycloak und POST /api/v1/themes/keycloak/apply (Themes.Manage). Der Schreiber selbst ist KeycloakLoginTheme.Write in Coworkee.Contracts.Theming.
In Produktion cacht Keycloak Themes. Teile ein Volume zwischen API und Keycloak (ThemeDirectory der API, /opt/keycloak/themes bei Keycloak), setze das Login-Theme des Realms einmal auf den ThemeName und starte Keycloak nach einer Änderung des Designs neu, oder starte es mit --spi-theme--cache-themes=false --spi-theme--cache-templates=false, wenn ein paar Millisekunden pro Seite in Ordnung sind. Ohne ThemeDirectory wird nichts geschrieben.