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

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). Brutalist style: high contrast, Space Grotesk, uppercase headings, thick black borders and hard offset 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.
#fcfbf8 · surface #ffffff · text #27241b · muted #676357 · primary #725e00 · accent #757ae3#15130e · surface #26241c · text #f4f4f1 · muted #b4b1a7 · primary #c2a200 · accent #b6beffPaste 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.
<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 & 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>/* Tokens (light + dark) */
:root{--bg:#fcfbf8;--surface:#ffffff;--border:#dddacf;--text:#27241b;--muted:#676357;--primary:#725e00;--on-primary:#ffffff;--accent:#757ae3;--font-h:"Space Grotesk",Georgia,serif;--font-b:"Space Grotesk",system-ui,sans-serif;--hw:700;
--c-bw:2px;--c-line:var(--text);--c-r:0;--c-pill:0;--c-sh:3px 3px 0 var(--text);--c-pop:6px 6px 0 var(--text);--c-tt:uppercase;--c-ls:.02em;--c-fw:700;--c-fs:13px;--c-ring:3px solid var(--text);--c-ind:4px;color-scheme:light}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#15130e;--surface:#26241c;--border:#3d3a32;--text:#f4f4f1;--muted:#b4b1a7;--primary:#c2a200;--on-primary:#111111;--accent:#b6beff;color-scheme:dark}
}
:root[data-theme="dark"]{--bg:#15130e;--surface:#26241c;--border:#3d3a32;--text:#f4f4f1;--muted:#b4b1a7;--primary:#c2a200;--on-primary:#111111;--accent:#b6beff;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(--text)}
.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.
## 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 text), never removed
- **Style knobs (brutalist):** border 2px, corners 0, shadow `3px 3px 0 var(--text)`, labels uppercase, tracked .02em
- **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.Add the Made Good Designs library to your assistant once. Then ask for UI references, brand palettes, font pairings and design tokens in any chat.
https://madegooddesigns.com/inspiration/mcp?ref=ui-card-uiSettings → Connectors → Add custom connector → paste the URL.
Settings → Apps & Connectors → Advanced → Developer mode → Create → paste the URL.
One click, then confirm in Cursor.
One click, then confirm in VS Code (Copilot agent mode).
claude mcp add --transport http made-good-designs https://madegooddesigns.com/inspiration/mcp?ref=ui-card-ui
gemini mcp add --transport http made-good-designs https://madegooddesigns.com/inspiration/mcp?ref=ui-card-ui
Add this to the app's MCP config (Windsurf: mcp_config.json).
Fonts: Space Grotesk (Google Fonts)
Prompt copied: paste it into the chat that just opened.
Writing about this component?
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.