How to build a dark minimal footer in React
A dark minimal footer in React centers the brand name, nav links, social links and copyright in a single vertical column using flexbox and CSS custom properties for theming. No external libraries needed; hover states are handled with inline onMouseEnter/onMouseLeave handlers.
- Stack: React (client component), CSS custom properties, zero external dependencies, ~130 lines.
- Animation: CSS-only, color transition on links at 0.15s, no Framer Motion.
- Layout: single centered flexbox column; all sections are optional and render only when data is provided.
- Accessible: native anchor tags, semantic footer element, text contrast at 40-50% white on dark background.
- Responsive by default; flexWrap on the nav row handles narrow viewports.
Footer Dark Minimal is a centered, column-stacked React footer for dark-themed sites. It renders a brand name as an uppercase link, an optional tagline, a wrapping nav row, a social row and a copyright line, all separated by a 40px hairline divider. Every section renders conditionally, so you drop in only what you need.
Anatomy
A single `<footer>` element wraps a centered container div (max-width from `--container-max-width`, padding from `--container-padding-x`). Inside, five stacked children: an `<a>` for the brand name (uppercase, 700 weight), an optional `<p>` for the description, a `<nav>` with wrapping flex row for page links, a flex row for social platform links, a 40px horizontal rule as a `<div>` (1px height, `--color-border-dark`), and a `<p>` for the copyright string. Gap between rows is 1.5rem.
How it works
The component is a straightforward presentational React function with no state or effects. Theming relies entirely on CSS custom properties (`--color-background-dark`, `--color-foreground-on-dark`, `--color-border-dark`) so it adapts to any preset without a line of JS. Hover on links is handled with onMouseEnter/onMouseLeave that mutate `e.currentTarget.style.color` directly, a deliberate choice to avoid a style sheet or CSS modules dependency while keeping the bundle minimal. The 40px divider between social links and copyright is a plain `<div>` acting as a visual pause before the legal line.
How to build it in React
Set up the outer shell
Create a `<footer>` with background set to `var(--color-background-dark)` and 3rem vertical padding. Inside, a centered `div` uses flexbox column with `alignItems: 'center'` and a 1.5rem gap. All children stack automatically.
<footer style={{ background: "var(--color-background-dark)", padding: "3rem 0" }}> <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "1.5rem" }}> {/* children */} </div> </footer>Brand name and description
Render the brand as an uppercase `<a href='/'>` at font-weight 700 and letter-spacing 0.05em. Wrap the description in a conditional block, only render when the string is non-empty. Cap it at max-width 400px and drop the opacity to 40% white so it recedes behind the brand.
{brandDescription && ( <p style={{ color: "rgba(255,255,255,0.4)", maxWidth: "400px", textAlign: "center" }}> {brandDescription} </p> )}Nav and social rows
Both rows are `display: flex` with a 1.5rem gap and `flexWrap: 'wrap'` on the nav so it breaks on small screens. Links start at 50% white opacity and step up to 80% on hover. Use onMouseEnter/onMouseLeave to set the color directly on `e.currentTarget.style`, no CSS class needed.
onMouseEnter={(e) => (e.currentTarget.style.color = "rgba(255,255,255,0.8)")} onMouseLeave={(e) => (e.currentTarget.style.color = "rgba(255,255,255,0.5)")}Hairline divider and copyright
Add a `<div>` that is 40px wide, 1px tall, with `background: var(--color-border-dark)` and a 0.5rem top/bottom margin. Below it, the copyright `<p>` drops to 11px (0.6875rem) at 25% white opacity so it barely registers visually, present for legal requirements, invisible to the eye.
<div style={{ width: "40px", height: "1px", background: "var(--color-border-dark)", margin: "0.5rem 0" }} /> <p style={{ fontSize: "0.6875rem", color: "rgba(255,255,255,0.25)" }}>{copyright}</p>
When to use it
Use it on dark-themed single-page sites, portfolios, agency pages, or SaaS landing pages where the footer is a closing punctuation mark rather than a navigation hub. It works best when the page already carries a strong visual identity and the footer just needs to exit cleanly. Skip it when you need multi-column link groups, newsletter sign-ups or heavy legal sections, the single-column layout has no room for that density.
Used by
FAQ
Can I add a logo image instead of the text brand name?
Replace the `<a>` text node with an `<img>` or an SVG inline component. Keep the anchor wrapper so the logo remains a home link; adjust the width to whatever fits your mark.
How do I change the link hover color from white to an accent color?
In the onMouseEnter handler, swap `rgba(255,255,255,0.8)` for your accent value, for example `var(--color-accent)`. Do the same for the onMouseLeave reset. No CSS class or extra state needed.
Is this footer accessible for keyboard and screen reader users?
The component uses a semantic `<footer>` element and native `<a>` tags, so keyboard navigation and screen readers work without extra ARIA. The low-opacity copyright text may fail WCAG AA contrast; add an `aria-label` with the full text if legal compliance requires it.
Why are hover styles handled with inline JS instead of a CSS class?
The component uses inline styles throughout to stay self-contained and avoid a dependency on CSS modules or a global stylesheet. The onMouseEnter/onMouseLeave pattern is the pragmatic trade-off for a simple, isolated component. If your project uses Tailwind, replacing inline styles with utility classes and the `hover:` variant is straightforward.