Primitive
Card CSS-only
A flexible content container with optional header, body, and footer sections. Five visual variants, status accents, and built-in interactive / selectable patterns. Pure CSS — no JavaScript required.
About
Overview
A card groups related content into a single visual unit — a profile, a stat, a settings panel, a list item, an article preview. RenDS gives you one base component with optional header / body / footer slots, five visual variants, and three interaction states (static, interactive, selectable).
Cards are entirely CSS-driven. There's no <ren-card> Web Component because there's nothing for JS to do — the structure is plain HTML, the variants are class modifiers, and the interactivity (hover, focus, selection) is handled by CSS pseudo-classes plus standard ARIA attributes.
When to use
- To group a chunk of content (text + actions + metadata) that the user will visually scan as a single unit.
- To present a list of similar items (a grid of products, posts, profiles, settings groups).
- For status surfaces — use the
data-statusattribute to signal success, warning, danger, or info at a glance.
When not to use
- For a single line of content — a card adds visual weight that body text doesn't need. Use a paragraph or a banner.
- For a modal dialog — that's
ren-dialog, which traps focus and dims the rest of the page. - For navigation between pages — wrap an anchor or use
ren-link; cards make poor primary navigation.
Cards are containers, not components. They don't dictate what goes inside. Mix and match — drop in a button group, an avatar, a chart, a form. The card supplies the surface; you supply the content.
Structure
Anatomy
A card has a container plus three optional section slots. Each slot has its own padding rules so they nest cleanly without you fighting margins.
Assembled
Project update
Last edited 2 hours ago
Dashboard ready for review. All components passed a11y audit.
The outer surface. Sets background (--color-surface-raised), border, radius, and clips children with overflow: hidden.
Optional. Top section for title + description. Stack of .ren-card-title + .ren-card-description.
The main content area. Padded uniformly. When following a header, top padding tightens automatically.
Optional. Bottom row for actions. Use .ren-card-footer-border for a separating line above the footer.
Live
Demo
The default card with all three sections. Drop in your own content — RenDS won't fight you.
Welcome back
Pick up where you left off
Three drafts saved, two pull requests waiting for review, and a deployment scheduled for tomorrow.
<div class="ren-card">
<div class="ren-card-header">
<h4 class="ren-card-title">Welcome back</h4>
<p class="ren-card-description">Pick up where you left off</p>
</div>
<div class="ren-card-body">
<p>Three drafts saved, two pull requests waiting for review…</p>
</div>
<div class="ren-card-footer">
<button class="ren-btn ren-btn-ghost ren-btn-sm">Later</button>
<button class="ren-btn ren-btn-primary ren-btn-sm">Open dashboard</button>
</div>
</div>
Sections are all optional. A card with only .ren-card-body works. A card with no sections at all (just children) auto-pads its first and last child via :first-child / :last-child selectors.
Catalog
Variants
Five visual variants — pick the one that matches the card's role. Plus three interaction states layered on top, and four status accents for at-a-glance signals.
Surface + border
Shadow, no border
Border, transparent bg
No surface, grouping only
Recessed surface
Hover lifts, click feedback
Click to set aria-selected
Accent border + ring
Reference
API
CSS classes only. There is no Web Component for card.
Container
| Class | Effect |
|---|---|
.ren-card | Base container. Required. |
.ren-card-elevated | Removes border, adds --shadow-md. For floating UI. |
.ren-card-outline | Border only, transparent background. Lighter weight. |
.ren-card-ghost | Removes both border and background. Pure structural grouping. |
.ren-card-sunken | Inverted depth — sits below the surface. Use sparingly. |
Sections
| Class | Use |
|---|---|
.ren-card-header | Top section. Stack of title + description with auto-spacing. |
.ren-card-title | Heading inside the header. Set on the actual heading tag (<h3>, <h4>, etc). |
.ren-card-description | Subtitle / metadata under the title. Muted color. |
.ren-card-body | Main content. Self-padded. |
.ren-card-footer | Bottom row, no separator. For action clusters. |
.ren-card-footer-border | Footer with a top border separator. Heavier visual weight. |
.ren-card-simple | Quick padding shorthand for cards without explicit sections. |
Interaction
| Class / attribute | Effect |
|---|---|
.ren-card-interactive | Hover lift + shadow + border emphasis. Adds cursor: pointer, focus ring on :focus-visible. Wrap in or apply to a focusable element (<a>, <button>, or tabindex="0"). |
.ren-card-selectable | Hover swaps border to accent. Combine with aria-selected="true" or data-selected for the chosen state. |
[data-status="success|warning|danger|info"] | Adds a 3px accent border on top in the matching status color. Stacks with any visual variant. |
Layout helper
| Class | Effect |
|---|---|
.ren-card-group | Auto-fill grid of cards at min(100%, 280px) per column. Drop on the parent of the cards. |
.ren-card-cover | For a full-bleed image at the top of a card. width: 100%; object-fit: cover. |
Inclusive by default
Accessibility
Static cards are non-interactive — they're just visual containers and don't need any ARIA. Interactive and selectable cards inherit semantics from the wrapping element you choose.
Static cards
- Use semantic content inside — proper heading levels (
<h2>–<h4>) for titles, real<p>tags for prose, real<button>s for actions. - Don't add ARIA roles to the card itself — it's a generic
<div>by intent. - For status accents, the visual
data-status="danger"top border isn't enough on its own. Pair with a text label, an icon, or anaria-describedbyfor screen reader users.
Interactive cards
If the entire card is clickable, wrap the card content in an <a> or <button>, or set tabindex="0" + role="button" if neither makes sense.
<a> or fires <button>'s click).<button>. Anchors don't respond to Space — use Enter.Don't nest interactive elements. An interactive card that contains a button creates a confusing focus order — Tab, Tab, Tab to land somewhere predictable. Pick one: either the whole card is clickable, or the card has internal buttons. Not both.
Selectable cards (radio / checkbox patterns)
- For exclusive choice (one of N), wrap each card in a
<label>with a hidden<input type="radio">, or userole="radio"+aria-checked. - For multi-select, use
role="checkbox"+aria-checked, or a hidden<input type="checkbox">in a label. - Set
aria-selected="true"on the chosen card so screen readers announce "selected". The CSS targets botharia-selectedanddata-selectedfor the accent ring.
Reduced motion
The hover-lift on interactive cards uses --duration-tactile and respects prefers-reduced-motion via the semantic motion tokens. Users who request reduced motion get a quieter transition (color change without scale).
Recipes
Examples
Common compositions. Copy the markup, swap the content.
KPI / stat card
For dashboards. One number, one label, optional trend.
Active users
12,430
↑ 12.5% vs last month
<div class="ren-card">
<div class="ren-card-body">
<p class="stat-label">Active users</p>
<p class="stat-value">12,430</p>
<p class="stat-trend">↑ 12.5% vs last month</p>
</div>
</div>
Selectable plan card (radio pattern)
For pricing tiers, plan switchers, theme pickers.
<label class="ren-card ren-card-selectable">
<input type="radio" name="plan" />
<div class="ren-card-body">
<strong>Pro</strong>
<p>$12/mo · unlimited</p>
</div>
</label>
Article preview (interactive card with cover)
Wrap the whole card in <a> so the entire surface is one click target.
<a href="/post/123" class="ren-card ren-card-interactive">
<div class="cover"></div>
<div class="ren-card-header">
<h4 class="ren-card-title">From Tailwind to vanilla…</h4>
<p class="ren-card-description">5 min read · Engineering</p>
</div>
<div class="ren-card-body">
<p>How we replaced a 180 KB toolchain…</p>
</div>
</a>
Danger-zone card
For destructive actions in settings panels. Status border + danger button.
Delete account
Permanently remove your account and all associated data.
<div class="ren-card" data-status="danger">
<div class="ren-card-header">
<h4 class="ren-card-title">Delete account</h4>
<p class="ren-card-description">Permanently remove…</p>
</div>
<div class="ren-card-footer-border">
<button class="ren-btn ren-btn-danger ren-btn-sm">Delete account</button>
</div>
</div>
Card group (auto-grid)
One class on the parent gives you a responsive grid of cards.
Auto-sized
Wraps responsively
No JS needed
<div class="ren-card-group">
<div class="ren-card">…</div>
<div class="ren-card">…</div>
<div class="ren-card">…</div>
</div>