Accessibility is a design system constraint, not a audit checkbox
When I designed elangodev.com, I wanted premium gradients, glass panels, and bold typography — the same visual language as blog and handbook pages. I also needed WCAG-conscious contrast, keyboard paths, and honest cookie consent before Google Analytics and AdSense load. Those requirements shaped components I reuse everywhere: CookieConsent, ThemeContext, blog prose classes, and PublicNav.
A design system on a solo portfolio is not Storybook with fifty variants. It is a handful of enforced patterns — semantic HTML first, ARIA when native elements are insufficient, dark mode tokens that preserve readability, and consent gates that respect privacy law and user trust.
CookieConsent: accept, reject, and real side effects
CookieConsent renders a fixed bottom dialog with role="dialog", aria-label, and aria-live="polite" so screen readers announce the banner when it appears. Two buttons — Essential Only and Accept All — map to CONSENT_ESSENTIAL and CONSENT_ACCEPTED in lib/consent.js. setConsent writes localStorage and dispatches cookie-consent-change so AnalyticsTracker and AdSenseScript react without reload.
export function hasAnalyticsConsent() {
return getStoredConsent() === CONSENT_ACCEPTED;
}
export function setConsent(value) {
localStorage.setItem(CONSENT_KEY, value);
window.dispatchEvent(
new CustomEvent("cookie-consent-change", { detail: value })
);
}Essential Only is not a dark pattern hidden in gray text — it is a peer button with equal visual weight to Accept All, placed first on mobile stacks for thumb reach. Privacy Policy links to /privacy with underline and sufficient color contrast in both light and dark themes. I wrote copy that names Google Analytics and AdSense explicitly because vague "we use cookies" language fails users and regulators.
ThemeContext and dark mode without flash
ThemeProvider in context/ThemeContext.jsx loads saved theme from localStorage on mount, toggles the dark class on document.documentElement for Tailwind dark: variants, and wraps MUI CssBaseline for dashboard widgets that still use Material components. Until mounted, it renders children without MUI theme to avoid hydration mismatch — a compromise I document so future refactors know why the tree briefly lacks MuiThemeProvider.
const changeTheme = useCallback((newTheme) => {
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
if (newTheme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, []);layout.jsx sets suppressHydrationWarning on html because theme class may differ between server HTML and client storage. Blog prose uses dark:prose-invert so long articles remain readable at night — slate body text, white headings, blue links with hover underline. I test contrast on prose-p and prose-headings pairs, not just hero sections.
Blog typography as a reusable prose token
PostDetailContent.jsx wraps article HTML in a long Tailwind prose class string — prose-lg, prose-slate, custom heading weights, relaxed paragraph leading, rounded images. Generated blog content from my editorial pipeline outputs semantic h2 sections; the prose layer standardizes spacing and font rhythm without inline styles in the database.
That separation lets me restyle every published post by editing one className. Headings use font-black tracking-tighter for brand alignment with the homepage hero. Links stay blue with underline on hover — never low-contrast gray on purple gradients inside article bodies.
PublicNav: keyboard, focus, and mobile drawer
PublicNav spans elangodev.com marketing routes — blog, services, handbook, contact. Desktop links are real anchor elements via Next Link, not div click handlers. On the homepage, section jumps use buttons only when already on / with explicit scrollIntoView behavior; elsewhere, hash links route home first.
Mobile menu toggles with AnimatePresence; focus should trap inside the drawer when open — an improvement still on my backlog — but icon buttons include aria labels on scroll-to-top and menu close controls elsewhere on the site. I avoid outline-none without replacement focus rings on interactive elements in new code.
Forms and auth accessibility
Login inputs pair visible labels with focus ring styles — ring-indigo-500/50 on keyboard focus. Error messages from Supabase auth render in text with AlertCircle icon and readable red on dark backgrounds. Submit buttons disable during isLoading to prevent double POST, with state communicated visually and via button opacity.
Dashboard modals use Drawer components with titled sections. I prefer native button type="button" for non-submit actions so Enter in text fields does not accidentally close dialogs.
Ads and analytics within consent boundaries
AdSenseScript returns null until consent — screen readers never encounter hidden ad iframes prematurely. When ads load, I place AdUnit components inside article flow with labels, avoiding deceptive placement near navigation that looks like menu items. AnalyticsTracker skips localhost and waits for idle time — reducing surprise network activity during keyboard navigation audits.
Process: how I keep aesthetics and a11y aligned
I run Lighthouse accessibility on /, /blog, and /login before major releases. I manually tab through CookieConsent and PublicNav. When I add blog and handbook pages, I check color contrast on generated CSS output — especially yellow-on-white in gradient generators.
Design tokens live in Tailwind config and component class strings I copy intentionally, not accidentally. Dark mode is not an inverted screenshot — I pick slate palettes that preserve separation between card, border, and text.
What inclusive premium design means to me
Balancing aesthetics and accessibility on elangodev.com means users who accept cookies, reject tracking, prefer dark mode, or navigate by keyboard all get a coherent experience. CookieConsent and ThemeContext are as much my design system as rounded-4xl nav pills and prose typography on blog posts.
I would rather ship one accessible pattern reused ten times than ten bespoke hero sections with unique contrast failures. That discipline is how my portfolio demonstrates engineering maturity — not only motion and cloud architecture, but interfaces that welcome every visitor who lands on elangodev.com.



