Skip to content

Commit 9a9f093

Browse files
authored
Add documentation about theming. (#4975)
1 parent 57fbb65 commit 9a9f093

1 file changed

Lines changed: 259 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Theming Hypha
2+
3+
Hypha's interface is built with [Tailwind CSS](https://tailwindcss.com/) and
4+
[daisyUI](https://daisyui.com/). Every colour, corner radius and border width comes from a small
5+
set of CSS custom properties, and those properties can be overridden from a single custom
6+
template — no fork, no frontend build, no recompiled assets.
7+
8+
A handful of overridden values is enough to make Hypha look like it belongs next to your
9+
organisation's other web properties, without touching a single Hypha template.
10+
11+
Two themes are defined, `light` and `dark`, and visitors switch between them (or follow their
12+
operating system) with the theme toggle in the footer. Whatever you change, change it in both.
13+
14+
## How the colours are organised
15+
16+
Hypha's defaults live in `hypha/static_src/tailwind/base/themes.css`. You do not need to edit
17+
that file — but it is worth knowing how it is put together, because it explains why so little
18+
CSS is needed to re-theme the whole application.
19+
20+
**1. The brand ramp.** A single brand colour, and ten shades derived from it:
21+
22+
```css
23+
:root {
24+
--color-brand: oklch(56.2% 0.102 243);
25+
--color-brand-base: 0.05;
26+
--color-brand-10: /* … derived from --color-brand … */;
27+
/* --color-brand-20 through --color-brand-100 */
28+
}
29+
```
30+
31+
The shades are generated from `--color-brand` with CSS relative colour syntax, so one colour
32+
produces a consistent light-to-dark ramp.
33+
34+
**2. The two daisyUI themes.** Each theme sets its own colours, mostly daisyUI defaults, with the
35+
primary colour taken from the brand ramp — a lighter step in dark mode, a slightly darker one in
36+
light mode:
37+
38+
```css
39+
@plugin "daisyui/theme" {
40+
name: "dark";
41+
--color-primary: var(--color-brand-50);
42+
/* … */
43+
}
44+
45+
@plugin "daisyui/theme" {
46+
name: "light";
47+
--color-primary: var(--color-brand-60);
48+
/* … */
49+
}
50+
```
51+
52+
The important consequence: `--color-brand` is defined **once**, outside both themes, so
53+
overriding that one value re-colours both themes at once.
54+
55+
## What each colour controls
56+
57+
| Variable | Where you see it in Hypha |
58+
| --- | --- |
59+
| `--color-primary` | Primary buttons, active navigation underline, link hover colour, primary badges. The most visible brand colour. |
60+
| `--color-primary-content` | Text and icons drawn *on top of* the primary colour. Must contrast with `--color-primary`. |
61+
| `--color-secondary` | Secondary buttons and badges. |
62+
| `--color-base-100` | Page and card backgrounds. |
63+
| `--color-base-200` | Panel, table header and sidebar backgrounds. |
64+
| `--color-base-300` | Borders, dividers and loading placeholders. |
65+
| `--color-base-content` | Body text. |
66+
| `--color-neutral` | A few high-contrast surfaces. |
67+
| `--color-info`, `--color-success`, `--color-warning`, `--color-error` | Status badges, alerts and form validation messages. Best left close to the conventional blue/green/amber/red so their meaning stays obvious. |
68+
| `--color-accent` | Not currently used by any Hypha template — changing it has no visible effect. |
69+
| `--radius-selector`, `--radius-field`, `--radius-box` | Corner radius of checkboxes/toggles, inputs/buttons, and cards/modals. All `0.25rem` by default. |
70+
| `--border` | Default border width. |
71+
72+
## Where to put your overrides
73+
74+
Create the file `hypha/templates_custom/includes/head_end.html`.
75+
76+
This template is included at the very end of `<head>`, after Hypha's own stylesheets, and exists
77+
precisely for additions like this. Anything you declare there wins over the defaults, because it
78+
comes later in the cascade. Nothing needs to be recompiled and no `collectstatic` run is
79+
needed — the change takes effect as soon as the file is in place.
80+
81+
Everything below goes inside that file.
82+
83+
## Change the brand colour
84+
85+
For most organisations, this is the whole job:
86+
87+
```html
88+
<style>
89+
:root {
90+
--color-brand: oklch(53.2% 0.106 162.8); /* forest green */
91+
}
92+
</style>
93+
```
94+
95+
Both themes pick up the new colour, because both derive their primary from the ramp. Primary
96+
buttons, the active navigation underline, link hover states and primary badges all change
97+
together, in light and dark mode.
98+
99+
Some starting points, with the equivalent hex value for reference:
100+
101+
| Brand | Hex | `--color-brand` |
102+
| --- | --- | --- |
103+
| Hypha default (blue) | `#2378a8` | `oklch(56.2% 0.102 243)` |
104+
| Forest green | `#1B7F5A` | `oklch(53.2% 0.106 162.8)` |
105+
| Teal | `#0F766E` | `oklch(51.1% 0.086 186.4)` |
106+
| Deep purple | `#6C3FBF` | `oklch(49.3% 0.190 294.6)` |
107+
| Navy blue | `#1D4ED8` | `oklch(48.8% 0.217 264.4)` |
108+
| Warm red | `#C8442A` | `oklch(56.9% 0.173 33.1)` |
109+
110+
To convert your own brand colour, paste the hex value into [oklch.com](https://oklch.com/). The
111+
three numbers are lightness, chroma (saturation) and hue. Keep lightness somewhere around
112+
`50–60%` so the derived ramp has room to go both lighter and darker; if your brand colour is very
113+
light or very dark, take its hue and chroma but pick a mid lightness here.
114+
115+
You can also override `--color-brand-base` in the same block. It controls how much colour is left
116+
at the extreme ends of the ramp: raise it towards `0.1` for more saturated tints and shades,
117+
lower it towards `0` for more neutral ones.
118+
119+
## Change individual colours
120+
121+
To go further than the brand colour, override the daisyUI variables directly. These are set
122+
*per theme*, so you must override them per theme too — otherwise your light-mode value leaks into
123+
dark mode:
124+
125+
```html
126+
<style>
127+
/* Light theme */
128+
:root,
129+
[data-theme="light"] {
130+
--color-primary: oklch(48% 0.11 162.8);
131+
--color-primary-content: oklch(100% 0 0);
132+
--color-secondary: oklch(45% 0.04 257);
133+
}
134+
135+
/* Dark theme */
136+
[data-theme="dark"] {
137+
--color-primary: oklch(70% 0.13 162.8);
138+
--color-primary-content: oklch(18% 0.02 162.8);
139+
--color-secondary: oklch(64% 0.03 229);
140+
}
141+
</style>
142+
```
143+
144+
Dark mode normally wants a lighter, less saturated version of the same colour, so it does not
145+
glare against the dark background.
146+
147+
!!! warning
148+
149+
Setting a per-theme variable on `:root` alone applies it to *both* themes. Hypha's own dark
150+
theme rule has the same specificity but comes earlier in the stylesheet, so your later
151+
declaration wins even when dark mode is active. `--color-brand` is the exception — it is not
152+
part of either theme, so `:root` is the correct place for it.
153+
154+
A softer, warmer set of backgrounds is a common second change. It tints every panel, table and
155+
card in the application:
156+
157+
```html
158+
<style>
159+
:root,
160+
[data-theme="light"] {
161+
--color-base-100: oklch(99% 0.004 85); /* off-white page background */
162+
--color-base-200: oklch(97% 0.006 85); /* panels and table headers */
163+
--color-base-300: oklch(93% 0.008 85); /* borders and dividers */
164+
}
165+
</style>
166+
```
167+
168+
Squarer or rounder corners are a one-liner, and apply to both themes:
169+
170+
```html
171+
<style>
172+
:root,
173+
[data-theme="light"],
174+
[data-theme="dark"] {
175+
--radius-field: 0.5rem; /* inputs and buttons */
176+
--radius-box: 0.75rem; /* cards and modals */
177+
}
178+
</style>
179+
```
180+
181+
## Larger changes: use a stylesheet file
182+
183+
Once you have more than a few declarations, move the CSS out of the template and into a file.
184+
Anything under the `public/` directory is served as a static file, so
185+
`public/css/theme-custom.css` can be linked from the same `head_end.html`:
186+
187+
{% raw %}
188+
```html
189+
{% load static %}
190+
<link rel="stylesheet" href="{% static 'css/theme-custom.css' %}">
191+
```
192+
{% endraw %}
193+
194+
Run `collectstatic` after adding or changing that file in production:
195+
196+
```bash
197+
python manage.py collectstatic --noinput --settings=hypha.settings.production
198+
```
199+
200+
## Alternative: edit the theme source and rebuild
201+
202+
If you already run Hypha from your own fork and build the frontend assets as part of your
203+
deployment, you can change the defaults directly in
204+
`hypha/static_src/tailwind/base/themes.css` instead. Edit `--color-brand` in the `:root` block,
205+
or set variables inside the two `@plugin "daisyui/theme"` blocks:
206+
207+
```css
208+
@plugin "daisyui/theme" {
209+
name: "light";
210+
/* … */
211+
--color-primary: oklch(48% 0.11 162.8);
212+
--color-primary-content: oklch(100% 0 0);
213+
--color-base-100: oklch(99% 0.004 85);
214+
/* … */
215+
}
216+
```
217+
218+
Then rebuild. In development, `make serve` watches the file and rebuilds as you save. For a
219+
production deployment:
220+
221+
```bash
222+
npm run build
223+
python manage.py collectstatic --noinput --settings=hypha.settings.production
224+
```
225+
226+
This is more work to carry across upgrades, but it is the only approach that also covers the 400
227+
and 404 error pages, which are standalone templates that do not include `head_end.html`.
228+
229+
## Logo and favicon
230+
231+
Colours are only part of blending in.
232+
233+
**The site logo** needs no code at all. Upload it in Wagtail admin under
234+
`Settings` -> `System settings`, where you can set a default logo, a separate mobile logo, and
235+
the URL the logo links to. Without one, Hypha falls back to its own logo.
236+
237+
**The favicons** are static files in `hypha/static_src/images/favicons/`, and the `<link>` tags
238+
for them are in `base.html`. Replacing them means either swapping those source files and
239+
rebuilding, or overriding `base.html` itself —
240+
[realfavicongenerator.net](https://realfavicongenerator.net/) produces the full set.
241+
242+
Surrounding markup such as `hypha/templates/includes/header-logo.html` can be overridden like any
243+
other template — see [Overriding templates](overriding-templates.md).
244+
245+
## Before you go live
246+
247+
- **Check both themes.** Use the theme toggle in the footer, and check it in a browser set to
248+
dark mode as well.
249+
- **Check the contrast.** Most colours have a matching `--color-*-content` for text drawn on top
250+
of them, and the `base-*` backgrounds share `--color-base-content`. If you darken
251+
`--color-primary`, its content colour probably needs to be white; if you lighten it, black.
252+
Aim for a contrast ratio of at least 4.5:1 — [oklch.com](https://oklch.com/) shows the ratio as
253+
you adjust a colour, and Hypha's [accessibility notes](../../references/accessibility.md) cover
254+
the wider commitments.
255+
- **Keep the status colours recognisable.** Applicants and reviewers rely on red meaning
256+
rejected and green meaning approved.
257+
- **The Wagtail admin keeps its own look.** These variables style the applicant, reviewer and
258+
staff interface; the Wagtail CMS admin is styled separately — see
259+
[Wagtail admin](../../references/wagtail-admin.md).

0 commit comments

Comments
 (0)