The site uses Tailwind CSS 3.3 for styling with a custom configuration that implements the Spearmint Ice brand system and dark mode support. Spearmint acts (buttons, links, brand); ice informs (secondary info, metadata). Surfaces, borders, and text use neutral grays; mint and ice are reserved for accents.
tailwind.config.cjs: Main Tailwind configurationtypography.cjs: Custom typography plugin settingssrc/styles/tailwind.css: Tailwind entry point
The TechHut brand color is spearmint. The brand reads as primary-500 (#4FE3A3) on dark surfaces and primary-800 (#08734F) on light surfaces.
| Name | Hex | Usage |
|---|---|---|
techhut |
#4FE3A3 |
Legacy alias — spearmint brand/action (dark surfaces, bright fills) |
techhut-dark |
#2FC488 |
Legacy alias — mint 600 hover |
techhut-light |
#6FEBB6 |
Legacy alias — mint 400 hover |
Prefer the primary-* scale below; the techhut* tokens exist for backwards compatibility.
Full mint ramp for UI elements. 500 is the dark-mode brand, 800 is the light-mode brand, 400/600 are hover steps:
primary-50: #E3FBF0 (lightest tint)
primary-100: #E3FBF0
primary-200: #B6F5DA
primary-300: #8FF0C7
primary-400: #6FEBB6 (dark-mode hover)
primary-500: #4FE3A3 (dark-mode brand)
primary-600: #2FC488
primary-700: #087850 (light-mode hover)
primary-800: #08734F (light-mode brand)
primary-900: #0A6B4C (darkest)
Secondary accent for informational UI (info states, metadata accents). Same logic: 500 on dark, 800 on light:
ice-50: #E4F9FF (lightest tint)
ice-100: #E4F9FF
ice-200: #BFF0FF
ice-300: #A8EDFF
ice-400: #7FE8FF
ice-500: #59E0FF (dark-mode info)
ice-600: #2CC3E8
ice-700: #0FA9CE
ice-800: #08748E (light-mode info)
ice-900: #0A6E86 (darkest)
zinc, slate, gray, and neutral are all remapped to a single neutral gray ramp, so existing gray classes stay consistent without a green tint:
50: #FAFAFA (off-white — light surface)
100: #F0F0F0 (paper raised)
200: #E5E5E5
300: #D4D4D4
400: #A3A3A3
500: #737373
600: #525252
700: #404040 (light-mode body)
800: #262626
900: #171717 (ink)
950: #080808 (deep)
| Name | Hex | Usage |
|---|---|---|
dark |
#080808 |
Deep — main dark surface |
dark-lighter |
#121212 |
Deep raised — cards, nav |
light |
#FAFAFA |
Off-white — main light surface |
ink |
#F5F5F5 |
Soft white — dark-mode headings |
ink-dim |
#D4D4D4 |
Dark-mode body text |
ink-faint |
#A3A3A3 |
Dark-mode muted text |
line |
#262626 |
Hairline borders (dark) |
line-strong |
#404040 |
Stronger borders |
UI feedback only — never in logos, thumbnails, or marketing:
| Name | Hex | Usage |
|---|---|---|
danger |
#FF7A70 |
Errors, destructive actions |
warning |
#FFC94D |
Warnings, cautions |
Bright mint/ice (the 300–500 steps) are never used as text color on light surfaces — they don't have enough contrast. Text on light uses the 800 step. Bright steps are fine as fills with dark text on top, in either mode:
{/* Text: mode-map the step */}
<a className="text-primary-800 dark:text-primary-500">Link</a>
{/* Fill: bright mint + dark text works everywhere */}
<button className="bg-primary-500 text-dark">Action</button>Borders stay 1px neutral — no primary/ice borders on cards. No gradients or glows on UI elements.
Dark mode is implemented using Tailwind's class strategy.
// tailwind.config.cjs
module.exports = {
darkMode: 'class',
// ...
}Use the dark: prefix for dark mode styles:
<div className="bg-zinc-50 dark:bg-dark text-zinc-900 dark:text-ink">
Content adapts to theme
</div>The ModeToggle component handles theme switching and persists preference to localStorage.
Typography is configured via @tailwindcss/typography plugin with custom settings in typography.cjs.
| Class | Font | Usage |
|---|---|---|
font-sans |
Inter | Body text (default) |
font-display |
Outfit | Headlines only, weights 700/800 |
font-mono |
JetBrains Mono | Labels, timestamps, code |
Display headings (hero titles, section headings) use font-display font-bold (or font-extrabold); body copy stays Inter. Metadata eyebrows (dates, roles, category labels) use mono — see Common Patterns.
| Class | Size | Line Height |
|---|---|---|
text-2xs |
0.75rem | 1.25rem |
text-xs |
0.8125rem | 1.5rem |
text-sm |
0.875rem | 1.5rem |
text-base |
1rem | 1.75rem |
text-lg |
1.125rem | 1.75rem |
text-xl |
1.25rem | 1.75rem |
text-2xl |
1.5rem | 2rem |
text-3xl |
1.875rem | 2.25rem |
text-4xl |
2.25rem | 2.5rem |
text-5xl |
3rem | 1.05 |
text-6xl |
3.75rem | 1.05 |
text-7xl |
4.5rem | 1 |
text-8xl |
6rem | 1 |
text-9xl |
8rem | 1 |
Article content uses the prose class for typography:
<article className="prose dark:prose-invert">
<MDXContent />
</article>Surfaces and navigation use square corners. Small controls retain a subtle radius:
| Class | Radius | Usage |
|---|---|---|
rounded-sm / rounded |
2px | Buttons, inputs, badges, chips |
rounded-md / rounded-lg |
0px | Cards, navigation links, sidebars |
rounded-xl / rounded-2xl / rounded-3xl |
0px | Panels, menus, code blocks |
rounded-full |
9999px | Avatars and true circles only |
Pill-shaped rounded-full is reserved for circular elements (avatars); rectangular chips and tags use rounded-sm.
Quick and precise, never bouncy. swift is the default ease for enters/exits; snap is for micro pops only (icons, toggles) — never on large surfaces:
| Token | Value | Usage |
|---|---|---|
ease-swift (default) |
cubic-bezier(0.2, 0, 0, 1) |
Enters, exits, most transitions |
ease-snap |
cubic-bezier(0.34, 1.56, 0.64, 1) |
Micro interactions only |
duration-micro |
150ms | Hovers, toggles |
duration-base |
300ms | Standard transitions |
duration-entrance |
500ms | Element entrances |
duration-reveal |
900ms | Large reveals |
Standard Tailwind breakpoints apply:
| Prefix | Min Width | Usage |
|---|---|---|
sm: |
640px | Small tablets |
md: |
768px | Tablets |
lg: |
1024px | Laptops |
xl: |
1280px | Desktops |
2xl: |
1536px | Large screens |
Styles are mobile-first. Base styles apply to all sizes, then use prefixes for larger screens:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{/* 1 column on mobile, 2 on tablet, 3 on desktop */}
</div>Extended max-width values:
| Class | Width |
|---|---|
max-w-lg |
33rem |
max-w-2xl |
40rem |
max-w-3xl |
50rem |
max-w-5xl |
66rem |
Extended opacity values:
| Class | Value |
|---|---|
opacity-1 |
0.01 |
opacity-2.5 |
0.025 |
opacity-7.5 |
0.075 |
opacity-15 |
0.15 |
Custom subtle elevation shadow:
<div className="shadow-glow">
{/* 0 0 4px rgb(0 0 0 / 0.1) */}
</div><div className="rounded-lg border border-zinc-200 dark:border-line bg-zinc-50 dark:bg-dark-lighter p-4">
Card content
</div>Bright mint fill always pairs with dark text:
<button className="bg-primary-500 hover:bg-primary-400 text-dark px-4 py-2 rounded-sm">
Primary Button
</button>Mode-map the brand step — 800 on light, 500 on dark:
<a className="text-primary-800 hover:text-primary-700 dark:text-primary-500 dark:hover:text-primary-400">
Styled Link
</a>Eyebrows (dates, author roles, category labels) use the mono accent — one per composition, never on titles or body:
<span className="font-mono text-2xs uppercase tracking-wider text-zinc-500 dark:text-ink-faint">
Jun 16, 2026
</span>Use focus-visible for keyboard navigation:
<button className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-800 dark:focus-visible:ring-primary-500">
Accessible Button
</button>Add custom values in tailwind.config.cjs:
module.exports = {
theme: {
extend: {
colors: {
custom: '#123456',
},
spacing: {
'18': '4.5rem',
},
},
},
}Add global CSS in src/styles/tailwind.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.custom-class {
@apply bg-primary-500 text-dark rounded-sm;
}
}The project uses these Tailwind plugins:
- @tailwindcss/typography: Prose styling for article content