The Nav Accent That Moved on Its Own Turned Out to Be Modulo
The AI entry in my navbar changed color after a menu reorder. A lesson in modulo index color assignment and fixing it with data identity.
TL;DR
Styling nav entries with a modulo index ties colors to render position, so reordering the menu silently reshuffles accents. Pinning the special "Ask AI" entry to its href identity keeps its color stable. Adding an idle pill also satisfies WCAG 1.4.1, since shape reinforces hue for users who can't distinguish colors.
That afternoon I opened the staging preview and the "Ask AI" entry in my navbar had turned green. It had been blue all week. I refreshed, checked another tab, and even suspected a bug in the theme CSS. After walking through the commits, it was no random glitch. Nav entry colors were assigned through a modulo index over an array, so whenever the order or the number of items changed, the accents shifted with their positions.
My first guess was the theme. The real problem was this line in Navbar.tsx:
color: NAV_COLORS[i % NAV_COLORS.length]
The pattern looks elegant. The color array rotates with modulo so the index never runs past the end. But there is a subtle cost that only shows up once the nav gains another item: the visual identity is tied to the render position, not to the data. The React docs describe the same trap in a different context, warning that using an index as identity often leads to subtle and confusing bugs [3]. The principle holds for any styling computed from an index.
Modulo Is Cheap to Write, Expensive to Maintain
With modulo, the "Ask AI" entry sitting in third place gets one color. Add a menu above it, it drops to fourth and swaps colors with another item. Nothing in the code errors out. But a user who knows "Ask AI" as the blue entry suddenly sees a navbar that feels different for no visible reason.
The fix is one condition. Because this nav has a special entry with its own route, I pinned its color to the href identity instead of the order:
const AI_COLOR = {
base: "hover:text-primary",
glow: "rgba(31,111,235,0.20)",
active: "text-primary",
} as const;
color: link.href === "/ai" ? AI_COLOR : NAV_COLORS[i % NAV_COLORS.length]
Now the "AI" entry keeps its accent wherever it sits. Interestingly, the first commit still hardcoded the hex value #1f6feb for hover and active. Six minutes later a follow-up commit replaced it with the primary theme token, and added a new idle field to every color object. Hardcoded hex values clearly have a short life once extra states arrive and need to stay consistent with the theme.
An Idle Pill, Because Color Alone Is Not Enough
That idle field holds pill classes for the inactive state: bg-primary/10 paired with text-primary, mirroring the active locale toggle. The number after the slash is Tailwind's color opacity modifier for controlling background opacity [2], and the underlying pattern is the same as hover variants that only apply on a specific state with no default effect [4]. Every entry now carries four states in one object: base, idle, hover, and active. The other color objects got an idle field too, but empty, so only the AI entry shows a pill when inactive.
The pill is not just decoration. WCAG 2.1 under Success Criterion 1.4.1 states that color must not be the only visual means of conveying information [1]. Before, the "AI" entry differed only by hue in its idle state, just like every other item. With the pill, it also differs in shape. Two channels at once, hue and form, mean users who struggle to tell colors apart can still recognize this entry.
There is a more practical reason too. WAI points out that a consistent visual position across pages makes it easier for people to find a mechanism again [5]. A special entry like an AI assistant works the same way. Users who know where it lives will be disappointed if it moves or changes style without warning. Visual consistency supports positional consistency.
The lesson I now hold on to: bind visual identity to data identity, not to render order. Modulo is pleasant while writing, one line and done. But maintaining it is expensive, because every reorder pays the cost somewhere else. The most stable anchor for styling is something that does not change, like an href, an id, or a slug, never a temporary position in an array.