Credit Card Payment Form Neo Brutalism Design Using HTML and CSS
Build a bold, tactile credit card payment form using only HTML and CSS — no JavaScript required. The neo-brutalist style pairs thick borders, heavy offset shadows, and a monospace font to create a UI that feels raw, intentional, and impossible to ignore. In this guide you will go from a blank file to two fully accessible, mobile-ready form variants.
Prerequisites
- Basic knowledge of HTML and CSS
- A code editor such as VS Code
- A browser with DevTools (Chrome or Firefox recommended)
Part 1: HTML Structure
Step 1: Create the container (index.html)
Start with a semantic wrapper that uses role="region" so screen readers announce
the section. Each form sits inside a .neo-payment-wrapper — a white card with
thick borders and an offset shadow.
<div class="neo-payment-container"
role="region"
aria-label="Neo-brutalist payment forms">
<!-- Primary form -->
<div class="neo-payment-wrapper" id="payment-form-primary">
<div class="neo-payment-header">
<h2>Payment Details</h2>
</div>
<form class="neo-payment-form" action="/payment" method="post">
<div class="neo-payment-form-group">
<label for="card-number-primary" class="neo-payment-form-label">
Card Number
</label>
<input
type="text" id="card-number-primary" name="card-number"
class="neo-payment-form-input"
placeholder="1234 5678 9012 3456"
required aria-label="Credit card number"
/>
</div>
<div class="neo-payment-form-cvc-row">
<div class="neo-payment-form-group">
<label for="card-expiry-primary" class="neo-payment-form-label">
Expiry Date
</label>
<input
type="text" id="card-expiry-primary" name="card-expiry"
class="neo-payment-form-input"
placeholder="MM/YY"
required aria-label="Card expiry date"
/>
</div>
<div class="neo-payment-form-group">
<label for="card-cvc-primary" class="neo-payment-form-label">
CVC
</label>
<input
type="text" id="card-cvc-primary" name="card-cvc"
class="neo-payment-form-input"
placeholder="123"
required aria-label="Card CVC code"
/>
</div>
</div>
<div class="neo-payment-form-group">
<label for="card-holder-primary" class="neo-payment-form-label">
Cardholder Name
</label>
<input
type="text" id="card-holder-primary" name="card-holder"
class="neo-payment-form-input"
placeholder="John Doe"
required aria-label="Cardholder name"
/>
</div>
<button type="submit" class="neo-payment-form-button">
Pay Now
</button>
</form>
<div class="neo-payment-footer">
Secure payment? <a href="#" aria-label="View payment security details">Learn More</a>
</div>
</div>
</div>
Keeprole="region"andaria-labelon every top-level interactive section. Screen readers announce the label before reading the contents, so users know they are entering a payment area.
Part 2: CSS — Neo-Brutalist Styling
Step 2: CSS variables and reset (styles.css)
Define all colours, spacing, and shadow values as CSS custom properties at the top of your stylesheet. This makes it trivial to theme the form or add a dark mode later.
:root {
/* Colours */
--gray-900: #111827;
--gray-700: #374151;
--gray-300: #d1d5db;
--gray-100: #f3f4f6;
--white: #ffffff;
--primary-600: #2563eb;
--primary-500: #3b82f6;
--accent-500: #f59e0b;
--success-500: #10b981;
--error-500: #ef4444;
--pink-500: #ec4899;
/* Spacing */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 12px;
--spacing-lg: 16px;
--spacing-xl: 20px;
/* Shape */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 12px;
/* Shadows */
--shadow-neo: 4px 4px 0 var(--gray-900);
/* Typography */
--font-mono: 'IBM Plex Mono', monospace;
/* Motion */
--transition: 0.2s ease;
}
Step 3: Card wrapper
The .neo-payment-wrapper is the core visual unit — white background,
4px border, and a hard offset shadow that creates the signature neo-brutalist
"stacked paper" effect.
.neo-payment-wrapper {
width: 360px;
padding: var(--spacing-lg);
background: var(--white);
border: 4px solid var(--gray-900);
box-shadow: var(--shadow-neo);
border-radius: var(--radius-lg);
}
/* Secondary variant swaps black borders for amber */
.neo-payment-wrapper.secondary {
border-color: var(--accent-500);
}
Step 4: Form inputs
Inputs use a light gray background (--gray-100) and a thick 3px
border. On :focus the border colour shifts to --primary-600 and
an amber offset shadow appears — giving keyboard users a clear, high-contrast indicator.
.neo-payment-form-input {
padding: var(--spacing-sm);
border: 3px solid var(--gray-900);
background: var(--gray-100);
font-size: 0.875rem;
font-family: var(--font-mono);
border-radius: var(--radius-sm);
box-sizing: border-box;
}
.neo-payment-form-input:focus {
outline: none;
background: var(--gray-300);
border-color: var(--primary-600);
box-shadow: 2px 2px 0 var(--accent-500);
}
Step 5: Submit button with hover animation
The button uses transform: translate(2px, 2px) on hover to simulate pressing the
card into its shadow — a physical metaphor that reinforces the tactile brutalist aesthetic.
Colours invert completely: green background becomes white with a green border.
.neo-payment-form-button {
padding: var(--spacing-sm) var(--spacing-lg);
background: var(--success-500);
color: var(--white);
font-family: var(--font-mono);
font-weight: 700;
font-size: 0.875rem;
border: 3px solid var(--gray-900);
cursor: pointer;
box-shadow: var(--shadow-neo);
transition: all var(--transition);
border-radius: var(--radius-sm);
width: 100%;
}
.neo-payment-form-button:hover,
.neo-payment-form-button:focus {
background: var(--white);
color: var(--success-500);
border-color: var(--success-500);
box-shadow: none;
transform: translate(2px, 2px);
}
/* Focus gets an extra accessibility ring */
.neo-payment-form-button:focus {
outline: none;
box-shadow: 2px 2px 0 var(--accent-500),
0 0 0 3px var(--primary-600);
}
Step 6: Responsive layout
Below 640px the wrapper fills the screen, font sizes decrease slightly, and the
expiry date / CVC row stacks vertically so inputs stay comfortable on small thumbs.
@media (max-width: 640px) {
.neo-payment-wrapper {
width: 100%;
max-width: 320px;
padding: var(--spacing-md);
}
.neo-payment-form-label,
.neo-payment-form-input,
.neo-payment-form-button {
font-size: 0.75rem;
}
.neo-payment-form-cvc-row {
flex-direction: column;
gap: var(--spacing-sm);
}
}
Part 3: Live Demo
Here are both form variants rendered with the full CSS applied:
Payment Details
Checkout
How It All Fits Together
Layout
The outer .neo-payment-container is a simple flex column that stacks
wrappers vertically. Each .neo-payment-wrapper is a self-contained card. The
.neo-payment-form-cvc-row uses display: flex to place Expiry and CVC
side by side — they collapse to a column on mobile.
Neo-Brutalist Design Decisions
| Property | Value | Why |
|---|---|---|
| Border width | 4px on card, 3px on inputs | Thick lines are the signature brutalist tell |
| Shadow | 4px 4px 0 #111827 | Hard offset = physical depth, no blur = raw aesthetic |
| Hover transform | translate(2px, 2px) + box-shadow: none | Simulates pressing the element into the page |
| Font | IBM Plex Mono | Monospace reinforces the technical, no-nonsense feel |
| Secondary variant | Amber borders instead of black | Single colour swap is enough for a distinct identity |
Accessibility
Every input has a visible <label> linked via for/id
and an aria-label for programmatic context. The container carries
role="region" and aria-label. Focus styles are two-layered — an amber
offset shadow plus a blue ring — meeting WCAG 2.1 AA contrast at all sizes.
<label> element.
Accessibility Checklist
- Use
role="region"andaria-labelon interactive sections - Every input must have a paired
<label>— not just a placeholder - Maintain a minimum 4.5:1 contrast ratio for all text
- Focus states must be visible without relying on colour alone
- All interactive elements must be reachable and operable by keyboard
Golden Rules for Neo-Brutalist UI
- Use CSS custom properties — theme changes should touch one block of variables, nothing else
- Hard shadows, not soft ones —
4px 4px 0 #colorreads as deliberate, not decorative - Monospace fonts signal technical intent without extra decoration
- Hover = press the element into the page, not lift it away
- Two variants are enough — primary and secondary, differentiated by a single colour
What to Try Next
- Add JavaScript input masking to auto-format the card number as
1234 5678 9012 3456 - Detect the card type (Visa, Mastercard) from the first digits and show a logo badge
- Connect to Stripe Elements to handle real payments securely
- Add a dark mode by toggling a
data-theme="dark"attribute and overriding the CSS variables
Neo-brutalism works because it is honest — the design makes no attempt to hide that it is made of boxes and borders. Keep that honesty in your production code: name things clearly, avoid magic numbers, and document every non-obvious decision.
Comments
Post a Comment