Theming an Angular application

Ever since we started building Hindsight a year ago, we’ve had a note about themes. We’ve always felt that users should get to choose between light and dark themes, to fit their workflow. We started out with a dark theme, because we know that the biggest problems — the ones where your application is down — rarely come at convenient times. Therefore, we optimised our theme to work at night, so that when alerts go off and you need to fix the problem immediately, you’re not completely blinded (and still able to sleep afterwards).

However, a lot of people view logs in the daytime as well. While many developers still prefer dark themes, there are just as many of us that use light themes, and like to keep our monitors as bright as the room we’re working in.

That’s why we wanted themes — to fit our users’ workflows, and adapt to our users’ needs.

Our Solution

The Hindsight frontend is written with Angular, so we had a think about the best way to implement dynamic class changes based on user preference, and realised that an Angular Directive would be a good fit.

We wrote a directive called hsThemed, which becomes an attribute you can add to any element. In addition to that, we added two input bindings, darkClass and lightClass, so that you can specify which class is applied for either theme (another option is to simply pass an object to the directive if you have a bunch of themes, but I thought it looked a lot nicer as separate attributes). The initial version looked like this:

The directive can then be used like this:

This will always apply what’s in the class attribute, and then conditionally append the lightClass or darkClass depending on what theme is currently active. We use Tailwind, and so this works really well with utility classes, but would work equally well with BEM modifiers (e.g class="sidebar" lightClass="sidebar--light")

A Slight Improvement

Shortly after coming up with the hsThemed directive, I realised another problem: we had some elements where we had consolidated all Tailwind classes of that element into one BEM-named class, and used Tailwind’s handy @apply PostCSS plugin. So some classes might have looked like this:

Clearly, this wouldn’t work with theming. However, I didn’t particularly feel like suddenly moving those two colour attributes out to the DOM (although doing so would definitely be a valid approach). Instead, I made a slight addition to our directive:

(this.el.nativeElement as HTMLElement).classList.add(`theme--${this.themingService.currentTheme}`);

I added the above snippet to the end of the ngOnInit call, which makes the directive always add a theme--{currentTheme} class to the element. This meant I was able to rewrite the CSS as follows:

Adding the light theme styling was then just a matter of another couple lines in the CSS.

The final code for the directive looks like this

And that’s all there’s to it!

Conclusion

Theming an application can be hard — you can’t simply invert the colours and hope everything works. However, with this directive, it allowed me to theme about 80% of Hindsight in less than two hours, including the time to write the directive.


Hindsight is a logging platform tailored for Laravel, meant to make logging accessible and effortless for everyone.