Most of my professional work sits under NDA, which limits what I can show publicly. So I built this — a case study that demonstrates how I think and work through design challenges.
This project isn't just about a logo or website. It's about applying design systems, accessibility, and UX principles to create a cohesive, performant, and extensible identity — one that could evolve just like a product.
Are you interested in working together? Let's talk about how I can help you build a better product or experience.
Every decision has purpose — whether that's a color choice improving contrast or a system pattern reducing cognitive load.
I design with three guiding principles:
Challenge
Most of my UX and design system work is protected by NDAs, leaving me without visual case studies to share.
Solution
Build a living system that showcases the same strategy, process, and craft I bring to client projects — through my own brand and website.
This project began as a design exercise: simplifying my family crest into something timeless and functional.
I explored how far I could distill a complex, symbolic form into a modern, modular identity. The result became more than an experiment — it evolved into my personal and freelance brand, now used across print, digital, and even on the cornhole boards at my wedding.
Each stroke and space follows a consistent modular ratio. The underlying grid supports not just the logo, but future assets like icons and patterns.
The mark works in monochrome, print, web, and motion. Its simplicity keeps it recognizable in any format — a key UX principle for brand trust and recall.
A pure monochrome palette — black, white, and grayscale tones — ensures contrast and timelessness. Key text and action values meet WCAG AAA standards, keeping the experience consistent across screens and environments.
--tc-text-primaryhsl(0, 0%, 4%)
--tc-text-secondaryhsl(0, 0%, 32%)
--tc-background-primaryhsl(0, 0%, 96%)
--tc-background-secondaryhsl(0, 0%, 92%)
--tc-borderhsl(0, 0%, 32%)
--tc-linkhsl(0, 0%, 4%)
--tc-actionhsl(0, 0%, 4%)
--tc-text-primaryhsl(0, 0%, 100%)
--tc-text-secondaryhsl(0, 0%, 92%)
--tc-background-primaryhsl(0, 0%, 4%)
--tc-background-secondaryhsl(0, 0%, 24%)
--tc-borderhsl(0, 0%, 92%)
--tc-linkhsl(0, 0%, 100%)
--tc-actionhsl(0, 0%, 100%)
Two fonts run the system: a clean sans for interface copy and a precision mono for technical detail.
Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm
Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz
0123456789 • ! ? & @ # ( ) [ ]
The quick brown fox jumps over the lazy dog.
abcdefghijklmnopqrstuvwxyz 0123456789
{ } ( ) [ ] ; : , . / * + - =
const theme = 'dark'; // small but readable
Using Font Awesome Pro gives me a scalable, accessible foundation for icons while enabling custom illustration integration. Because it's font-based, it inherits accessibility, weight control, and adaptability across contexts.
Accessibility drives every decision.
As a CPACC-certified designer, I see accessibility not as a checklist but as good UX — improving clarity, predictability, and comfort for everyone.
I maintain a Figma Accessibility Checklist that guides contrast, motion, labeling, and focus behavior. It's a living document used to test and iterate on design decisions before they reach production.
This website is the latest evolution of years of experimentation — from static HTML, to multiple WordPress iterations, experimentation with Hugo, Jekyll, and many others — now distilled into a fully self-contained, dependency-free system that's back-to-basics.
The theme toggle uses CSS variables and localStorage to persist user preference — small script, high UX impact.
HTML
Drop a single script in the head and the toggle button in the nav. The icons swap based on theme state so the action is always clear.
<head>
<script src="assets/scripts/scripts.js"></script>
<link rel="stylesheet" href="assets/styles/style.css">
</head>
<header id="Top">
<a href="index.html" aria-label="Home">
Tyler Coderre
</a>
<nav aria-label="Primary">
<ul>
<li><a href="about.html">About</a></li>
<li><a href="index.html#work">Work</a></li>
<li><a href="index.html#contact">Contact</a></li>
</ul>
<button type="button" data-theme-toggle aria-label="Switch to dark mode" aria-pressed="false">
<i class="fa-sharp fa-regular fa-lightbulb-slash" data-theme-icon="light" aria-hidden="true"></i>
<i class="fa-sharp fa-regular fa-lightbulb-on" data-theme-icon="dark" aria-hidden="true"></i>
</button>
</nav>
</header>
CSS
The palette is inverted by swapping the root tokens, with a soft transition for the mode change and a reduced-motion fallback.
:root {
--tc-root-400-light: hsl(0, 0%, 4%);
--tc-root-300-light: hsl(0, 0%, 32%);
--tc-root-200-light: hsl(0, 0%, 92%);
--tc-root-100-light: hsl(0, 0%, 100%);
--tc-root-400-dark: hsl(0, 0%, 96%);
--tc-root-300-dark: hsl(0, 0%, 92%);
--tc-root-200-dark: hsl(0, 0%, 24%);
--tc-root-100-dark: hsl(0, 0%, 4%);
--tc-root-400: var(--tc-root-400-light);
--tc-root-300: var(--tc-root-300-light);
--tc-root-200: var(--tc-root-200-light);
--tc-root-100: var(--tc-root-100-light);
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) {
--tc-root-400: var(--tc-root-400-dark);
--tc-root-300: var(--tc-root-300-dark);
--tc-root-200: var(--tc-root-200-dark);
--tc-root-100: var(--tc-root-100-dark);
color-scheme: dark;
}
}
html[data-theme="dark"] {
--tc-root-400: var(--tc-root-400-dark);
--tc-root-300: var(--tc-root-300-dark);
--tc-root-200: var(--tc-root-200-dark);
--tc-root-100: var(--tc-root-100-dark);
color-scheme: dark;
}
html,
body {
transition: background-color 0.4s ease, color 0.4s ease;
}
@media (prefers-reduced-motion: reduce) {
html,
body {
transition: none;
}
}
body > header button[data-theme-toggle] {
border: 1px solid transparent;
background: transparent;
padding: 0;
width: 1.875rem;
height: 1.875rem;
margin: 0;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
border-radius: 2rem;
margin-left: auto;
overflow: visible;
transition: background-color 0.25s ease, color 0.25s ease;
}
html:not([data-theme]) body > header button[data-theme-toggle] {
display: none;
}
body > header button[data-theme-toggle]::after {
content: none;
}
body > header button[data-theme-toggle] i {
font-size: 0.95rem;
line-height: 1;
}
body > header button[data-theme-toggle]:hover,
body > header button[data-theme-toggle]:focus,
body > header button[data-theme-toggle]:focus-visible {
background: var(--tc-background-secondary);
color: var(--tc-text-primary);
border-color: transparent;
}
nav button[data-theme-toggle] [data-theme-icon] {
display: none;
}
html[data-theme="dark"] nav button[data-theme-toggle] [data-theme-icon="dark"] {
display: inline-flex;
}
html:not([data-theme="dark"]) nav button[data-theme-toggle] [data-theme-icon="light"] {
display: inline-flex;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) nav button[data-theme-toggle] [data-theme-icon="light"] {
display: none;
}
html:not([data-theme]) nav button[data-theme-toggle] [data-theme-icon="dark"] {
display: inline-flex;
}
}
JavaScript
The script resolves theme from user preference first, falls back to system, and persists the choice across pages with no extra markup.
const root = document.documentElement;
const storageKey = 'tc-theme';
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const isFileProtocol = window.location.protocol === 'file:';
const canPersistStorage = (() => {
try {
localStorage.setItem('__tc_theme_test__', '1');
localStorage.removeItem('__tc_theme_test__');
return true;
} catch (error) {
return false;
}
})();
const shouldUseLinkTheme = isFileProtocol || !canPersistStorage;
const getStoredTheme = () => {
try {
return localStorage.getItem(storageKey);
} catch (error) {
return null;
}
};
const getNameTheme = () => {
if (!window.name) {
return null;
}
if (window.name === 'dark' || window.name === 'light') {
return window.name;
}
try {
const data = JSON.parse(window.name);
if (data && (data[storageKey] === 'dark' || data[storageKey] === 'light')) {
return data[storageKey];
}
} catch (error) {
return null;
}
return null;
};
const getUrlTheme = () => {
const params = new URLSearchParams(window.location.search);
const theme = params.get('theme');
if (theme === 'dark' || theme === 'light') {
return theme;
}
return null;
};
const setStoredTheme = (theme) => {
try {
localStorage.setItem(storageKey, theme);
} catch (error) {
// Ignore storage errors (private mode, disabled storage, etc.)
}
};
const setNameTheme = (theme) => {
try {
let data = {};
if (window.name && window.name !== 'dark' && window.name !== 'light') {
try {
data = JSON.parse(window.name) || {};
} catch (error) {
data = {};
}
}
data[storageKey] = theme;
window.name = JSON.stringify(data);
} catch (error) {
window.name = theme;
}
};
const getUserTheme = () => {
const storedTheme = canPersistStorage ? getStoredTheme() : null;
if (storedTheme === 'dark' || storedTheme === 'light') {
return storedTheme;
}
const nameTheme = getNameTheme();
if (nameTheme === 'dark' || nameTheme === 'light') {
return nameTheme;
}
return null;
};
const resolveTheme = () => {
const urlTheme = getUrlTheme();
if (urlTheme) {
return urlTheme;
}
const userTheme = getUserTheme();
if (userTheme) {
return userTheme;
}
return mediaQuery.matches ? 'dark' : 'light';
};
const applyTheme = (theme) => {
const nextTheme = theme === 'dark' ? 'dark' : 'light';
root.setAttribute('data-theme', nextTheme);
updateThemeColor();
};
const meta = document.querySelector('meta[name="theme-color"]');
const updateThemeColor = () => {
if (!meta) {
return;
}
const value = getComputedStyle(root).getPropertyValue('--tc-background-primary').trim();
if (value) {
meta.setAttribute('content', value);
}
};
const updateThemeLinks = (theme) => {
if (!shouldUseLinkTheme) {
return;
}
const links = document.querySelectorAll('a[href]');
const currentTheme = theme === 'dark' ? 'dark' : 'light';
links.forEach((link) => {
const href = link.getAttribute('href');
if (!href || href.startsWith('#') || href.startsWith('//')) {
return;
}
if (/^[a-z][a-z0-9+.-]*:/i.test(href)) {
return;
}
const [base, hash] = href.split('#');
const [path, queryString] = base.split('?');
const params = new URLSearchParams(queryString || '');
params.set('theme', currentTheme);
const nextHref = `${path}?${params.toString()}${hash ? `#${hash}` : ''}`;
link.setAttribute('href', nextHref);
});
};
const initialTheme = resolveTheme();
if (getUrlTheme() === initialTheme) {
if (canPersistStorage) {
setStoredTheme(initialTheme);
}
setNameTheme(initialTheme);
}
applyTheme(initialTheme);
const initThemeToggle = () => {
const toggle = document.querySelector('[data-theme-toggle]');
const setTheme = (theme, persist = false) => {
const nextTheme = theme === 'dark' ? 'dark' : 'light';
applyTheme(nextTheme);
if (toggle) {
const isDark = nextTheme === 'dark';
toggle.setAttribute('aria-pressed', isDark ? 'true' : 'false');
toggle.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode');
toggle.setAttribute('title', isDark ? 'Switch to light mode' : 'Switch to dark mode');
}
if (persist) {
if (canPersistStorage) {
setStoredTheme(nextTheme);
}
setNameTheme(nextTheme);
}
updateThemeLinks(nextTheme);
};
const userTheme = getUserTheme();
const systemTheme = mediaQuery.matches ? 'dark' : 'light';
setTheme(userTheme || systemTheme);
if (!userTheme) {
mediaQuery.addEventListener('change', (event) => {
const latestTheme = getUserTheme();
if (latestTheme) {
return;
}
setTheme(event.matches ? 'dark' : 'light');
});
}
if (toggle) {
toggle.addEventListener('click', () => {
const currentTheme = root.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
const nextTheme = currentTheme === 'dark' ? 'light' : 'dark';
setTheme(nextTheme, true);
});
}
};
const initPage = () => {
updateThemeLinks(root.getAttribute('data-theme'));
updateThemeColor();
initClock();
initFormValidation();
initThemeToggle();
initDialogs();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPage);
} else {
initPage();
}
Preference remembered, instantly applied.
I approached this like any product design challenge — define, test, refine, document.
This system is built on universal UX principles:
| Principle | Application |
|---|---|
| Clarity | Simple forms, high contrast, clear typography. |
| Consistency | Predictable patterns build user trust and reduce learning curves. |
| Feedback | Accessible focus and hover states reassure users their input matters. |
| Performance | Fast load times and reduced dependencies improve experience and confidence. |
| Scalability | Tokenized structure makes it easy to grow without redesigning. |
Tools evolve, but the mindset stays the same.
I used Illustrator and Affinity Designer for vector and logo work, Photoshop and Affinity Photo for images, Figma for systems and page design, and VS Code + GitHub for build and version control.
I'm always exploring new tools — though it's come a long way from the days of editing CSS in Sublime Text with no syntax highlighting and pure faith.
The system scales naturally into templates, social media assets, and printed materials — all using the same grid, type, and contrast tokens.
That consistency reinforces trust and familiarity across mediums.
Every iteration teaches refinement. Constraints created focus, and simplicity led to flexibility.
Next steps include exploring lightweight micro interactions, adding user preference storage for accessibility options, and expanding the design system for multi-theme applications.
Minimalism isn't about removing — it's about revealing what matters. This project became proof that accessibility, performance, and UX all strengthen each other when treated as one discipline.
It's a live case study that continues to evolve, just like the work I create for clients.
This case study will keep growing with new experiments and refinements. It's not a snapshot — it's a living document of how I approach design problems.
If you're looking for a designer who blends design systems, accessibility, and UX strategy with the precision of a developer — you're already seeing the results.
You can contact and connect with me through email, on Dribbble, or LinkedIn as well.