Engineering · Part 11 · define it once
One account widget, every shell
Reference documentation for a single account widget — the top-right "who am I / my account" control — rendered identically across a logged-out marketing site, the authentication pages, and the signed-in application, from one template and one script. A small thing, done once, that quietly removes a recurring class of inconsistency.
Almost every web app has the same fixture: a corner control that shows "Log in / Sign up" when signed out and a user menu (name, account links, log out) when signed in. The usual failure is that it gets built two or three times — once for the marketing pages, once for the app — and the copies drift: the marketing one lacks the Billing link the app grew, the account menu shows different items depending on where you happen to be. This document is the antidote: one component, included by every shell.
The problem is architectural, not visual
A typical app has several shells — the outer templates page families extend. A marketing shell (wide, boosted navigation), an auth shell (a centred card), an application shell (sidebar + content). It is natural to give each its own header, and fatal to give each its own account control, because then "the account menu" is three things that must be kept in sync by discipline alone. The fix is to make the account control a component that all three shells include, so there is exactly one definition and drift is impossible:
<!-- marketing shell --> <div class="topbar-actions">{% include 'account_widget.html' %}</div>
<!-- auth shell --> <div class="topbar-actions">{% include 'account_widget.html' %}</div>
<!-- app shell --> <div class="topbar-actions">{% include 'account_widget.html' %}</div>
Now the widget is defined in one file and appears in every context. Add the Billing link once and it is everywhere; there is no "the marketing version" to forget.
The component: one template, both states
The widget owns the whole signed-in/signed-out decision itself, so no shell has to. Signed out, it is two links; signed in, it is an accessible menu-button with a dropdown:
{% if user.is_authenticated %}
<div class="user-menu{% if menu_up %} user-menu--up{% endif %}">
<button class="user-menu-trigger" data-user-menu-trigger
aria-haspopup="true" aria-expanded="false">
<svg class="icon"><use href="#i-account"/></svg>
<span>{{ user.get_full_name|default:user.email }}</span>
</button>
<div class="user-menu-panel" role="menu">
<a role="menuitem" href="{% url 'dashboard' %}">Dashboard</a>
<a role="menuitem" href="{% url 'billing' %}">Billing</a>
<a role="menuitem" href="{% url 'account' %}">Account</a>
{% if user.is_staff %}
<div class="user-menu-sep" role="separator"></div>
<a role="menuitem" href="{% url 'staff_home' %}">Staff tools</a>
{% endif %}
<a role="menuitem" href="{% url 'logout' %}">Log out</a>
</div>
</div>
{% else %}
<a href="{% url 'login' %}">Log in</a>
<a class="btn small" href="{% url 'signup' %}">Sign up</a>
{% endif %}
Three things make one definition serve every context:
- The auth state lives in the component. Because the widget branches on
user.is_authenticateditself, the marketing shell — which mostly serves logged-out visitors but sometimes a logged-in one — shows the right control automatically. No shell passes a flag; the component knows. - Role differences are conditionals, not copies. Staff-only links sit behind
{% if user.is_staff %}in the same menu, so a staff member sees the same widget everyone sees plus their extra items — there is never a separate "staff header". - One positioning knob. A single optional parameter (
menu_up) flips the dropdown to open upwards, for the one placement — a menu pinned to the bottom of a sidebar — where down would open off-screen. One component, one flag, covers even that.
Behaviour that survives htmx swaps
The dropdown's interactivity — open on hover or click, close on click-away or Escape, arrow-key navigation — lives in one small script, and the interesting requirement is that it must keep working across htmx navigation (Part 9), where parts of the page are swapped without a full reload. Bind idempotently, from both the initial load and htmx's post-swap event, and mark bound elements so re-initialisation is a no-op:
function init() {
document.querySelectorAll('.user-menu:not([data-bound])').forEach(function (menu) {
menu.dataset.bound = '1'; // bind each widget exactly once
// … wire hover, click-to-pin, Escape, arrow keys on `menu` …
});
}
document.addEventListener('DOMContentLoaded', init);
document.addEventListener('htmx:load', init); // re-run after any swap; the :not([data-bound]) guard makes it safe
if (document.readyState !== 'loading') init(); // and if the script loads late
The :not([data-bound]) selector plus the data-bound stamp is the whole
trick: init() can run any number of times — on first load, after every htmx swap, after
a boosted navigation — and only ever wires each widget once. Without this, a boosted page navigation
that re-renders the header would either lose the menu's behaviour or double-bind its handlers. With
it, the widget "just works" no matter how the surrounding page arrived.
One small interaction detail worth stealing: allow both hover and click to open, but only let a
click-pinned menu toggle shut on a second click. A menu opened by hover, then clicked (as a
test driver or a deliberate user does), should stay open and become pinned rather than
immediately closing — otherwise hover-then-click flickers the menu shut. Track a
pinned flag and the interaction feels right for mouse, keyboard, and automation alike.
Accessibility comes for free when there is one of it
A hidden benefit of a single component: you implement the accessible menu-button pattern —
aria-haspopup, aria-expanded tracking the open state,
role="menu"/role="menuitem", focus moving to the first item on open,
arrow-key traversal, Escape returning focus to the trigger — once, and every shell inherits
it. Three hand-rolled account menus would have three subtly different, probably incomplete,
accessibility stories. One has one correct story, tested in one place.
Why this tiny thing earns a place in the series
The account widget is trivial in isolation, and that is exactly the point. The same instinct that gave you one write path (Part 1), one tool catalogue (Part 4), one config reader (Part 8) applies at the smallest scale too: anything that appears in more than one place should be defined in exactly one place. A shared header control is the most visible daily instance of that principle — every engineer touches it, every user sees it — so getting it right, once, is a disproportionate amount of consistency for a single small template and a 60-line script. The components that "aren't worth abstracting" are precisely the ones that drift.
The shape to copy
- Make the account control a component every shell
{% include %}s — never a per-shell reimplementation. - Put the signed-in/signed-out branch and the role conditionals inside the component, so no shell passes state and there is no "marketing version".
- Bind its behaviour idempotently from load and the htmx post-swap event, guarded by a
data-boundstamp, so it survives partial and boosted navigation. - Implement the accessible menu-button pattern once; every shell inherits it.
That completes the series — from a domain core serving many transports, out to a remote MCP server, through testing, an agent, the box it runs on, how that box is built and paid for and configured, the UI it renders, the media behind that UI, and finally the smallest shared control on every page. The thread throughout is one idea at every scale: define each thing once, and let everything reuse it.