Credit Card Payment Form Neo Brutalism Design Using HTML and CSS

Credit Card Payment Form Neo Brutalism Design Using HTML and CSS

Rustcode
— min read
Article Preview Credit card payment form on a screen

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.

What you will build: Two payment form variants — a primary green "Pay Now" form and a secondary pink "Checkout" form — both responsive and keyboard-accessible.

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>
Keep role="region" and aria-label on 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

PropertyValueWhy
Border width4px on card, 3px on inputsThick lines are the signature brutalist tell
Shadow4px 4px 0 #111827Hard offset = physical depth, no blur = raw aesthetic
Hover transformtranslate(2px, 2px) + box-shadow: noneSimulates pressing the element into the page
FontIBM Plex MonoMonospace reinforces the technical, no-nonsense feel
Secondary variantAmber borders instead of blackSingle 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.

Never rely on placeholder text as a label substitute. Placeholders disappear on input and are not reliably announced by screen readers. Always pair every input with a visible <label> element.

Accessibility Checklist

  • Use role="region" and aria-label on 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 #color reads 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