Forbidden (403)
Clean error page for access-denied scenarios — pure CSS, responsive, and accessible.
Quick implementation
/* HTML:
<div class="error-403-container" role="alert" aria-label="403 Forbidden error">
<div class="error-lock">🔒</div>
<div class="error-code">403</div>
<div class="error-title">Forbidden</div>
<p class="error-message">You don't have permission to access this resource.</p>
<div class="error-actions">
<a href="/" class="btn--primary-small">Go Home</a>
<a href="javascript:history.back()" class="btn--outline">Go Back</a>
</div>
</div>
*/
.error-403-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 16rem;
padding: 2rem;
text-align: center;
}
.error-code {
font-family: var(--font-display);
font-size: clamp(4rem, 12vw, 8rem);
font-weight: 800;
line-height: 1;
color: var(--accent);
letter-spacing: -0.04em;
margin-bottom: 0.5rem;
}
.error-title {
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 700;
color: var(--text);
margin-bottom: 1rem;
letter-spacing: -0.02em;
}
.error-message {
color: var(--muted);
font-size: 1rem;
max-width: 20rem;
margin-bottom: 1.5rem;
line-height: 1.6;
}
.error-actions {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
justify-content: center;
}
.error-lock {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.9;
}
.btn--outline {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.6rem 1.25rem;
border: 1.5px solid var(--card-border);
border-radius: var(--radius);
background: transparent;
color: var(--text);
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease;
}
.btn--outline:hover,
.btn--outline:focus-visible {
background: var(--card);
border-color: var(--accent);
outline: none;
}
.btn--outline:focus-visible {
box-shadow: 0 0 0 3px oklch(0.6 0.2 250 / 0.25);
}
.btn--primary-small {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.6rem 1.25rem;
border: none;
border-radius: var(--radius);
background: var(--accent);
color: oklch(0.2 0.02 250);
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: background 0.15s ease;
}
.btn--primary-small:hover,
.btn--primary-small:focus-visible {
background: var(--accent-hover);
outline: none;
}
.btn--primary-small:focus-visible {
box-shadow: 0 0 0 3px oklch(0.6 0.2 250 / 0.25);
}
@media (prefers-reduced-motion: reduce) {
.btn--outline,
.btn--primary-small {
transition: none;
}
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer specializing in accessible error pages.
Goal: Build a 403 Forbidden error page with clear messaging and helpful navigation options.
Technical constraints:
- Use flexbox for centering layout with column direction on the error container.
- Use oklch() for all colors (accent, text, muted, borders) instead of hex or rgba().
- Primary action button uses var(--accent), secondary uses transparent with border.
- Focus rings use oklch() with alpha channel for the focus-visible state.
- Include @media (prefers-reduced-motion: reduce) to remove transitions for users who prefer it.
Framework variant (pick one):
A) Vanilla HTML + CSS only — return the .error-403-container structure with semantic HTML and all CSS.
B) React component — accept `message` (string), `homeUrl` (string), `onBack` (function), and `className` props; return TSX + CSS module.
Edge cases to handle:
- Ensure the error container has role="alert" and an aria-label for screen readers.
- Provide both "Go Home" and "Go Back" options so users aren't stuck.
- Handle mobile viewports with flex-wrap on the action buttons to prevent overflow.
Return the HTML structure and the CSS, clearly separated.
Why this matters in 2026
Error pages are the first (and often last) impression when access is denied. A well-crafted 403 page reduces frustration by clearly explaining what went wrong and offering actionable next steps. Modern CSS lets you build these without JavaScript — flexbox handles centering, clamp() makes the code responsive, and var(--accent) ties it to your design system.
The logic
Flexbox centering: display: flex; flex-direction: column; align-items: center; justify-content: center; on a container with min-height: 16rem ensures the error is always vertically and horizontally centered, regardless of viewport size. Responsive type: font-size: clamp(4rem, 12vw, 8rem) scales the code smoothly between mobile and desktop. Color system: Using var(--accent) for the code makes it visually pop while staying on-brand.
Accessibility & performance
The error container uses role="alert" to announce itself to screen readers immediately. The lock emoji is wrapped in aria-hidden="true" since it's decorative. Both buttons have visible focus states with :focus-visible and a halo using oklch() with alpha. For reduced-motion users, transitions are stripped out via @media (prefers-reduced-motion: reduce). No JavaScript is required — the entire UI is CSS-driven.