UI Branding & Theming
MindRouter can be rebranded per deployment so a single installation matches one institution's visual identity — organization name, logos, favicon, and accent colors — across the entire web UI, in both light and dark mode.
This is an installation-wide setting (not per-user or per-group). Every user of the deployment sees the same brand.
Mirrored from docs/branding.md in the MindRouter repository. Example hostnames are placeholders — substitute your own deployment.
Where
Admin menu → Branding (/admin/branding). Viewing requires admin-read;
saving requires a full admin (is_admin). All changes are recorded in the admin
audit log (branding.* actions).
What you can customize
| Field | Applies to |
|---|---|
| Organization / app name | Top navbar, browser tab titles, footer |
| Tagline | Footer, beside the name |
| Accent color — light theme | Buttons, links, focus rings, stat-card accents (light mode) |
| Accent color — dark theme | Same, in dark mode |
| Headline color | Headlines such as the email blog/notification title; defaults to a neutral near-black (light accents read poorly as headline text). Buttons/links keep the accent. |
| Logo — dark theme | Top navbar (always a dark background), plus footer and login card in dark mode |
| Logo — light theme | Footer and login card in light mode |
| Favicon | Browser tab icon |
Logos appear in the navbar (header), the footer, and the login card. The navbar always has a dark background, so it uses the dark-theme logo (falling back to the light one); the footer and login card follow the active theme and swap between the two logo variants automatically.
The live preview on the page shows a mock navbar, buttons, link, and stat card in both light and dark themes, updating as you pick colors. Nothing is applied site-wide until you click Save.
Use Reset to defaults to restore the stock MindRouter name, colors, and remove all uploaded assets.
Accessible accent colors (contrast handling)
A brand accent is often a light color (e.g. University of Idaho Pride Gold
#F1B300) that would be illegible with the default white button text. For each
theme's accent, MindRouter derives two accessible companions so light accents
stay readable:
--mr-accent-on— the foreground used on an accent fill (button text). White by default; flips to black when white would fall below a 3.0 contrast ratio on the accent (matching Bootstrap's convention: white on blue/red, dark on gold/yellow). A gold button gets black, legible text.--mr-accent-ink— the accent used as text on the page background (links,.text-primary, outline-button text, active sidebar item). Darkened (light page) or lightened (dark page) only as far as needed to reach a 4.5:1 WCAG contrast ratio. Fills, borders, and focus rings keep the true brand color.
Stock mid-tone accents (default blue #0d6efd) are left untouched — the
derivation only intervenes when the accent would otherwise be unreadable. The
math lives in _best_fg / _accessible_ink in
backend/app/services/branding.py.
Emails
Outgoing emails (blog-post notifications, admin/bulk notifications, the test email) follow the same brand — the blue default header is gone. Because email is a constrained medium, the treatment differs from the web UI:
- No CSS variables or SVG. Email clients strip
<style>and don't render SVG, so all styling is inline and the logo must be a raster image. Upload a PNG/JPG/GIF to the Email logo slot on the branding page (separate from the navbar SVG logos). A horizontal logo on a transparent/white background works best. - The logo is embedded, not linked. It's attached inline via
cid:(the message becomesmultipart/related), so it displays even when the client blocks remote images. If no email logo is set, the header shows the organization name as text. - Accent colors are contrast-safe. The header carries a thin accent rule, the
blog-post button uses the accent as its fill with the accessible foreground
(black text on gold), and links/headings use the darkened
ink— so a light brand accent stays legible on the white email background.
Email templates live in backend/app/services/email_service.py (_wrap_html
builds the branded shell; _send_one performs the CID embed).
How it works
- Text/color values are stored as
branding.*rows in theapp_configkey/value table (crud.get_config_json/crud.set_config). - Uploaded logos/favicon are written to
BRANDING_STORAGE_PATH(default/data/branding, a persistentbranding_datavolume) and served, cache-busted, from the public route/branding/asset/{filename}— public so logos also render on the login page. - Accent colors are injected into
base.htmlas CSS custom properties (--bs-primaryand friends) scoped to:rootand[data-bs-theme="dark"], layering on top of the existing Bootstrap 5 theming. Only validated hex values are ever emitted, so the injection is safe. - Branding is read on every page from an in-memory cache
(
backend/app/services/branding.py) that is loaded at startup and refreshed every ~15s, so a save propagates to all uvicorn workers within a few seconds without a restart. The saving worker refreshes immediately.
Deployment notes
BRANDING_STORAGE_PATH and BRANDING_MAX_LOGO_MB must be present in
settings.py and docker-compose.yml (pydantic-settings only reads env
inside the container). The branding_data named volume and the Dockerfile
mkdir /data/branding keep uploaded assets across container rebuilds.
Notes / limits
- Logo/favicon uploads are capped at
BRANDING_MAX_LOGO_MB(default 4 MB). Allowed logo types: PNG, JPG, WebP, SVG, GIF. Favicon: ICO, PNG, SVG. - The top navbar keeps its dark background by design (safe contrast in both themes); the accent color drives buttons, links, and highlights rather than the navbar itself.
- The public landing page marketing copy is not templated by this feature — branding covers the app chrome (navbar, titles, footer, login, colors, logos).