Skip to content
NativeCSS
Docs contents

Cards

The Component Reference only lists class names - this page walks through HOW a card is actually assembled from those building blocks, step by step, live on this page.

1. Base structure

.ncss-card is deliberately structure-only: a flex column with overflow: clip, no styling of its own. .ncss-card-body is the only required building block - header/footer/media are optional.

<article class="ncss-card">
  <div class="ncss-card-body">
    <h3>Title</h3>
    <p>Text ...</p>
  </div>
</article>

Result (live, same classes):

Title

Text ...

On its own the card is barely visible - no border, no rounding, no shadow. That's intentional, not a missing step: the styling comes next.

2. Styling: border, corners, shadow

Border/corners/shadow are deliberately NOT part of .ncss-card itself - that's a design decision, not a card property. Instead use the general-purpose utilities from helpers/elevation.css, which work identically on any other element:

<article class="ncss-card ncss-border ncss-radius-lg ncss-shadow-sm ncss-shadow-hover-md">
  <div class="ncss-card-body">
    <h3>Classic card</h3>
    <p>Border, rounded corners, subtle shadow, grows on hover.</p>
  </div>
</article>

Classic card

Border, rounded corners, subtle shadow, grows on hover.

Shadow only

No border, a stronger shadow instead - any combination is valid.

3. Media (image/placeholder)

.ncss-card-media as the FIRST child produces the usual "image on top" card, aspect-ratio defaults to --ncss-aspect-video. One important gotcha: .ncss-card-media itself sets border-radius: 0 and relies on the card's own overflow: clip to automatically round only the correct corners (the top ones, in this layout). Without this reset, combining media with e.g. .ncss-placeholder (which brings its own rounding) would round ALL four corners, including the bottom ones that never touch a card edge.

<article class="ncss-card ncss-border ncss-radius-lg ncss-shadow-sm">
  <div class="ncss-card-media">
    <img src="photo.jpg" alt="...">
  </div>
  <div class="ncss-card-body">
    <h3>With image</h3>
  </div>
</article>

Media on top

Natural document order, no extra class needed.

Media at the bottom

Just put .ncss-card-media as the LAST child, after .ncss-card-body - it automatically rounds the bottom corners instead of the top ones.

5. Variants

ClassEffect
.ncss-card--flushNo more padding in header/body/footer (not even top/bottom) - content reaches every card edge, e.g. for an image or a list without the usual card spacing
.ncss-card--transparentRemoves the only remaining decoration (background-color) - combined with --flush and without .ncss-border/.ncss-radius-*/.ncss-shadow-*, the card becomes an invisible wrapper while keeping the flex-column structure
.ncss-card--brand/-brand-2/-neutral/-success/-warning/-danger"Status" card: subtle tint background + colored accent border, for notice/status content - not meant as a general "colorful card" decoration
.ncss-card--linked + .ncss-card-linkWhole card clickable (teaser pattern) - the link stays the only interactive focus point (stretched link instead of wrapping the entire card in <a>, see below)

Saved successfully

Status variant, no border/shadow needed.

Whole card clickable

Only the title link is interactive in the markup - ::after visually stretches it across the whole card, so screen readers don't have to announce several nested interactive elements.

<article class="ncss-card ncss-card--linked ncss-border ncss-radius-lg ncss-shadow-sm">
  <div class="ncss-card-body">
    <h3><a class="ncss-card-link" href="/article">Whole card clickable</a></h3>
    <p>Further text, images etc. stay normal - only the link itself is focusable.</p>
  </div>
</article>

6. Horizontal (media next to text)

Media on the LEFT/RIGHT needs its own class (the flex direction has to switch) AND an extra .ncss-card-container wrapper, because an element can't query its OWN container-query context by spec - only descendants can. From 24rem CONTAINER width (not viewport!) the card automatically switches to side-by-side.

<div class="ncss-card-container">
  <article class="ncss-card ncss-card--horizontal ncss-border ncss-radius-lg">
    <div class="ncss-card-media"><img src="photo.jpg" alt="..."></div>
    <div class="ncss-card-body">
      <h3>Title</h3>
      <p>Text ...</p>
    </div>
  </article>
</div>

Live - shrink the browser window/panel to see it wrap at 24rem:

Horizontal

.ncss-card--horizontal-end mirrors the media to the other side (row-reverse), otherwise identical.

7. Advanced: adaptive radius for mobile

A common problem: a card should look nicely rounded on wide screens, but deliberately goes edge-to-edge on mobile (e.g. inside .ncss-full-bleed, or simply because the surrounding container drops its padding at that width) - the rounded corners then suddenly look wrong, because the card touches the screen edge directly. Instead of toggling that by hand with a @media breakpoint, there's .ncss-radius-adaptive-sm/-md/-lg (helpers/elevation.css): it only rounds when the card actually has room around it.

<article class="ncss-card ncss-border ncss-radius-adaptive-lg ncss-shadow-sm">
  ...
</article>

Live - drag the slider to make the box narrower/wider (an <iframe> establishes its OWN viewport, so the effect is testable with the slider instead of having to shrink the whole browser window):

<article class="ncss-card ncss-border ncss-radius-adaptive-lg ncss-shadow-sm"
         style="width: min(100vw, 20rem);">
  ...
</article>

The principle (the classic "conditional border radius" trick, see tokens.css' --ncss-radius-adaptive-* for the formula): 100vw (real viewport width) is compared against the element's own 100% (its own width) - when both are practically equal, the radius flips to 0. This compares against the REAL viewport, not any ancestor container - so it doesn't help inside a narrower, self-scrolling subregion, but that's exactly why it works reliably inside the <iframe> above (its vw refers to the iframe's own width). On a normal page, a card that should deliberately go edge-to-edge on mobile is usually combined with .ncss-full-bleed - IMPORTANT: .ncss-full-bleed itself needs an ancestor chain centered all the way to the viewport (calc(50% - 50vw)), otherwise it breaks out asymmetrically from an off-center layout like this doc page (a sidebar grid) - which is exactly why this demo uses an iframe instead of .ncss-full-bleed directly on the page. For components with their own radius escape hatch (e.g. .ncss-stack-card's --ncss-stack-radius, see demo/stacked-cards.html), set var(--ncss-radius-adaptive-lg) directly as the value instead of using the ready-made class.