← Back to UI screens

Live: try it with the mouse and the keyboard (Tab, arrows, Escape).

ScreenshotCards component — mobile, retro style
Theme

Cards component — mobile, retro style

Cards UI component for websites and web apps, shown in Juniper Supply, a fictional commerce product. Shows a responsive grid of product cards with one clickable link per card, a sale badge, a sold-out card, and every card state (default, hover, focus-visible, selected, disabled). Retro style: warm cream, soft Fraunces headings with DM Sans, outlined cards and offset colour shadows. Comes with copyable HTML, CSS and a few lines of plain JavaScript (keyboard support and ARIA per the WAI-ARIA Authoring Practices), a live demo, and a DESIGN.md with light and dark colour tokens.

Open live HTML ↗ Download DESIGN.md
Pattern
Cards
Device
Mobile
Style
Retro
Product (fictional)
Juniper Supply
Category
Commerce

Spec

Colours (light)
background #fdf7f0 · surface #f4eadd · text #281f12 · muted #6e6151 · primary #b1313d · accent #41953f
Colours (dark)
background #130c04 · surface #22190e · text #f6f1ea · muted #bbafa0 · primary #f98083 · accent #79ce75
Fonts
Fraunces (headings), DM Sans (body), JetBrains Mono (code) · Google Fonts, under their own licences
Light and dark
Both included; follows the system setting
ARIA pattern
Semantic HTML, no widget role needed
Keyboard
One Tab stop per card; Enter opens it
States
default, hover, focus-visible, selected, disabled
cardsmobilecommercecardbadgeimage

Code

Paste the HTML where the component goes, the CSS into your stylesheet and the script before </body>. The tokens block holds the light and dark colours; swap in your own.

HTML
<ul class="mgd-cards" aria-label="Juniper Supply new arrivals">
  <li><article class="mgd-card">
    <div class="mgd-card__media"><svg viewBox="0 0 200 120" aria-hidden="true" focusable="false"><rect x="70" y="30" width="60" height="58" rx="8" fill="color-mix(in oklab,var(--accent) 55%,var(--surface))"/><path d="M60 30h80l-12 22H72z" fill="color-mix(in oklab,var(--primary) 60%,var(--surface))"/><rect x="62" y="92" width="76" height="8" rx="4" fill="color-mix(in oklab,var(--text) 25%,var(--surface))"/></svg></div><span class="mgd-card__badge">New</span>
    <div class="mgd-card__body"><p class="mgd-card__cat">Coffee &amp; tea</p>
      <h3 class="mgd-card__title"><a class="mgd-card__link" href="#product-1">Stoneware pour-over set</a></h3>
      <p class="mgd-card__text">Hand-glazed dripper, carafe and two cups.</p>
      <p class="mgd-card__foot"><span class="mgd-card__price">$48</span></p></div>
  </article></li>
  <li><article class="mgd-card">
    <div class="mgd-card__media"><svg viewBox="0 0 200 120" aria-hidden="true" focusable="false"><circle cx="92" cy="62" r="34" fill="color-mix(in oklab,var(--text) 70%,var(--surface))"/><circle cx="92" cy="62" r="26" fill="color-mix(in oklab,var(--text) 55%,var(--surface))"/><rect x="124" y="57" width="52" height="10" rx="5" fill="color-mix(in oklab,var(--accent) 50%,var(--text))"/></svg></div><span class="mgd-card__badge">Sale</span>
    <div class="mgd-card__body"><p class="mgd-card__cat">Cookware</p>
      <h3 class="mgd-card__title"><a class="mgd-card__link" href="#product-2">Carbon steel skillet, 26 cm</a></h3>
      <p class="mgd-card__text">Pre-seasoned, oven-safe, lighter than cast iron.</p>
      <p class="mgd-card__foot"><span class="mgd-card__price">$54 <s><span class="mgd-sr">was </span>$68</s></span></p></div>
  </article></li>
  <li><article class="mgd-card">
    <div class="mgd-card__media"><svg viewBox="0 0 200 120" aria-hidden="true" focusable="false"><rect x="50" y="28" width="100" height="70" rx="6" fill="color-mix(in oklab,var(--primary) 35%,var(--surface))"/><path d="M50 50h100M50 72h100M80 28v70M115 28v70" stroke="color-mix(in oklab,var(--primary) 60%,var(--surface))" stroke-width="3"/></svg></div>
    <div class="mgd-card__body"><p class="mgd-card__cat">Table</p>
      <h3 class="mgd-card__title"><a class="mgd-card__link" href="#product-3">Washed linen napkins (set of 4)</a></h3>
      <p class="mgd-card__text">Soft from the first use. Machine washable.</p>
      <p class="mgd-card__foot"><span class="mgd-card__price">$32</span></p></div>
  </article></li>
  <li><article class="mgd-card mgd-card--soldout" aria-labelledby="card-t3" aria-describedby="card-s3">
    <div class="mgd-card__media"><svg viewBox="0 0 200 120" aria-hidden="true" focusable="false"><rect x="72" y="36" width="56" height="64" rx="12" fill="color-mix(in oklab,var(--accent) 35%,var(--surface))"/><rect x="78" y="24" width="44" height="14" rx="4" fill="color-mix(in oklab,var(--text) 45%,var(--surface))"/></svg></div><span class="mgd-card__badge" id="card-s3">Sold out</span>
    <div class="mgd-card__body"><p class="mgd-card__cat">Storage</p>
      <h3 class="mgd-card__title" id="card-t3">Glass pantry jar, 1.5 L</h3>
      <p class="mgd-card__text">Airtight bamboo lid. Back in January.</p>
      <p class="mgd-card__foot"><span class="mgd-card__price">$18</span></p></div>
  </article></li>
</ul>
CSS
/* Tokens (light + dark) */
:root{--bg:#fdf7f0;--surface:#f4eadd;--border:#d5c9ba;--text:#281f12;--muted:#6e6151;--primary:#b1313d;--on-primary:#ffffff;--accent:#41953f;--font-h:"Fraunces",Georgia,serif;--font-b:"DM Sans",system-ui,sans-serif;--hw:700;
--c-bw:2px;--c-line:var(--text);--c-r:10px;--c-pill:10px;--c-sh:3px 3px 0 var(--primary);--c-pop:5px 5px 0 var(--text);--c-tt:none;--c-ls:0;--c-fw:700;--c-fs:14px;--c-ring:3px solid var(--accent);--c-ind:3px;color-scheme:light}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#130c04;--surface:#22190e;--border:#382f23;--text:#f6f1ea;--muted:#bbafa0;--primary:#f98083;--on-primary:#111111;--accent:#79ce75;color-scheme:dark}
}
:root[data-theme="dark"]{--bg:#130c04;--surface:#22190e;--border:#382f23;--text:#f6f1ea;--muted:#bbafa0;--primary:#f98083;--on-primary:#111111;--accent:#79ce75;color-scheme:dark}

/* Component */
.mgd-sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.mgd-cards{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:20px}
.mgd-card{position:relative;display:flex;flex-direction:column;height:100%;background:var(--surface);border:var(--c-bw) solid var(--c-line);border-radius:var(--c-r);box-shadow:var(--c-sh);overflow:hidden;transition:transform .14s,box-shadow .14s,border-color .14s}
.mgd-card__media{aspect-ratio:5/3;display:grid;place-items:center;background:color-mix(in oklab,var(--primary) 8%,var(--bg));border-bottom:var(--c-bw) solid var(--text);}
.mgd-card__media svg{width:100%;height:100%}
.mgd-card__body{display:flex;flex-direction:column;gap:6px;padding:16px 16px 18px;flex:1}
.mgd-card__cat{margin:0;font:600 11px/1.2 var(--font-b);letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.mgd-card__title{margin:0;font:var(--hw) 17px/1.25 var(--font-h)}
.mgd-card__link{color:inherit;text-decoration:none}
.mgd-card__link::after{content:"";position:absolute;inset:0;border-radius:inherit}
.mgd-card__text{margin:0;color:var(--muted);font-size:14px}
.mgd-card__foot{margin-top:auto;padding-top:8px;display:flex;align-items:center;justify-content:space-between;gap:8px}
.mgd-card__price{font:600 16px/1 var(--font-b);font-variant-numeric:tabular-nums}
.mgd-card__price s{color:var(--muted);font-weight:400;font-size:13px;margin-left:6px}
.mgd-card__badge{position:absolute;top:12px;left:12px;padding:4px 10px;border-radius:var(--c-pill);font:700 11px/1.2 var(--font-b);text-transform:var(--c-tt);letter-spacing:var(--c-ls);background:var(--text);color:var(--bg)}
.mgd-card:hover,.mgd-card.is-hover{transform:translateY(-2px);box-shadow:5px 5px 0 var(--primary)}
.mgd-card:hover .mgd-card__link,.mgd-card.is-hover .mgd-card__link{text-decoration:underline;text-underline-offset:3px}
.mgd-card:has(.mgd-card__link:focus-visible),.mgd-card.is-focus{outline:var(--c-ring);outline-offset:3px}
.mgd-card__link:focus-visible{outline:none}
.mgd-card.is-on{border-color:var(--primary);box-shadow:0 0 0 2px var(--primary)}
.mgd-card.is-on .mgd-card__cat::after{content:" · in your basket";color:var(--text)}
.mgd-card--soldout{background:color-mix(in oklab,var(--surface) 60%,var(--bg))}
.mgd-card--soldout .mgd-card__media{filter:grayscale(1);opacity:.6}
.mgd-card--soldout .mgd-card__badge{background:var(--surface);color:var(--text);border:1px solid var(--muted)}
.mgd-card--soldout:hover{transform:none;box-shadow:var(--c-sh)}
@media (prefers-reduced-motion:reduce){.mgd-card,.mgd-card *{transition:none!important;animation-duration:2.4s!important}}

No JavaScript needed.

DESIGN.md

Component section (states, ARIA, keyboard)
## Component: Cards

Product cards with one link per card: the whole card is clickable, but screen readers hear a single, clear link name.

- **Pattern:** Semantic HTML (no ARIA widget role needed)
- **Roles and attributes:** Each card is an <article> with a heading; the heading's link covers the card with a ::after overlay, so there is one tab stop and one link name per card. A sold-out card uses aria-disabled on its button and keeps its text readable.
- **States drawn:** default, hover, focus-visible, selected, disabled
- **Focus:** a visible `:focus-visible` outline (3px solid accent), never removed
- **Style knobs (retro):** border 2px, corners 10px, shadow `3px 3px 0 var(--primary)`, labels sentence case
- **Motion:** transitions are short (≤160ms) and switched off under `prefers-reduced-motion: reduce`
- **Code:** HTML + CSS + no JavaScript; colours come only from the tokens above, so light and dark both work

### Keyboard
- **Tab:** move from card to card (one stop each)
- **Enter:** open the focused card

### Licence
Original Made Good Designs code: free to use and adapt in any project, including commercial ones, with no credit needed. Fonts are Google Fonts under their own licences.
AI

Build this with your AI

Fonts: Fraunces + DM Sans (Google Fonts)

Writing about this component?

More cards components

Cards component — web, retro (Juniper Supply)
web · retro
Cards component — mobile, minimal (Juniper Supply)
mobile · minimal
Cards component — mobile, editorial (Juniper Supply)
mobile · editorial
Cards component — mobile, brutalist (Juniper Supply)
mobile · brutalist
Cards component — mobile, luxury (Juniper Supply)
mobile · luxury
Cards component — mobile, playful (Juniper Supply)
mobile · playful
Cards component — mobile, corporate (Juniper Supply)
mobile · corporate
Cards component — mobile, monochrome (Juniper Supply)
mobile · monochrome
Building with an AI coding agent? Every screen here is also in the free Made Good Designs MCP server: ask Claude, Cursor or ChatGPT to search_ui_screens (e.g. “billing settings, editorial style”), get_ui_flow for a whole journey, and get_ui_screen_code to pull the HTML and DESIGN.md straight into your project.

Save to board