20 CSS Cards14 / 20

Pure CSSMIT licensed

Portfolio Project Card

Portfolio tile with a CSS-drawn dashboard mockup (top bar, side nav, KPI row, bar chart), category chip, project title, description, tags, and a client + "View project →" footer.

Published Updated

Live Demo
Try it

The code

<section class="card-14">
  <article class="card-14__card">
    <div class="card-14__thumb">
      <div class="card-14__mock" aria-hidden="true">
        <div class="card-14__mock-top"></div>
        <div class="card-14__mock-body">
          <div class="card-14__mock-side">
            <span></span><span></span><span></span><span></span>
          </div>
          <div class="card-14__mock-main">
            <div class="card-14__mock-kpi"><span></span><span></span><span></span></div>
            <div class="card-14__mock-chart">
              <span style="height:35%"></span>
              <span style="height:55%"></span>
              <span style="height:40%"></span>
              <span style="height:70%"></span>
              <span style="height:60%"></span>
              <span style="height:85%"></span>
              <span style="height:50%"></span>
              <span style="height:75%"></span>
            </div>
          </div>
        </div>
      </div>
      <div class="card-14__overlay" aria-hidden="true"></div>
    </div>
    <div class="card-14__body">
      <span class="card-14__chip">SAAS &middot; DASHBOARD</span>
      <h3 class="card-14__title">Meridian Analytics Rebrand</h3>
      <p class="card-14__desc">Redesigned the analytics dashboard for 12k+ enterprise users — 41% faster time-to-insight.</p>
      <ul class="card-14__tags">
        <li>Figma</li>
        <li>Design System</li>
        <li>Data Viz</li>
      </ul>
      <div class="card-14__foot">
        <span class="card-14__client">Meridian Labs</span>
        <a class="card-14__link" href="#project">View project <span aria-hidden="true">→</span></a>
      </div>
    </div>
  </article>
</section>
.card-14 {
  width: 100%;
  min-height: 100vh;
  display: block;
  padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 40px);
  box-sizing: border-box;
  background: #f8f7f4;
  font-family: 'Inter', system-ui, sans-serif;
  --focus: #b8613b;
}

.card-14 *,
.card-14 *::before,
.card-14 *::after {
  box-sizing: border-box;
}

.card-14__card {
  width: 100%;
  max-width: 340px;
  margin-left: auto;
  margin-right: auto;
  background: #fff;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid rgba(0,0,0,0.06);
  box-shadow: 0 2px 12px rgba(0,0,0,0.04);
  transition: transform 0.25s, box-shadow 0.25s;
}

.card-14__card:hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 32px rgba(184,97,59,0.14);
}

.card-14__thumb {
  position: relative;
  aspect-ratio: 16/9;
  background: linear-gradient(135deg, #f7edd6, #e8c9a0);
  overflow: hidden;
}

.card-14__mock {
  position: absolute;
  inset: 14px;
  background: #fff;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(60,30,10,0.15);
  transition: transform 0.35s ease;
}

.card-14__card:hover .card-14__mock {
  transform: scale(1.04);
}

.card-14__mock-top {
  height: 12px;
  background: #f0eae1;
  border-bottom: 1px solid rgba(0,0,0,0.06);
}

.card-14__mock-body {
  display: grid;
  grid-template-columns: 40px 1fr;
  height: calc(100% - 12px);
}

.card-14__mock-side {
  background: #faf7f2;
  padding: 8px 6px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.card-14__mock-side span {
  display: block;
  height: 5px;
  border-radius: 2px;
  background: #e0d5c4;
}

.card-14__mock-side span:first-child {
  background: #b8613b;
}

.card-14__mock-main {
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.card-14__mock-kpi {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
}

.card-14__mock-kpi span {
  height: 20px;
  background: #f0eae1;
  border-radius: 3px;
}

.card-14__mock-chart {
  flex: 1;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  padding-top: 4px;
}

.card-14__mock-chart span {
  flex: 1;
  background: linear-gradient(180deg, #d97e4a, #b8451f);
  border-radius: 2px 2px 0 0;
}

.card-14__overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.05));
  transition: opacity 0.3s;
}

.card-14__card:hover .card-14__overlay {
  opacity: 0;
}

.card-14__body {
  padding: 16px 18px 18px;
}

.card-14__chip {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: #b8613b;
  background: rgba(184,97,59,0.1);
  padding: 4px 9px;
  border-radius: 999px;
  margin-bottom: 10px;
}

.card-14__title {
  font-size: 16px;
  font-weight: 700;
  margin: 0 0 6px;
  color: #1a1a1a;
  letter-spacing: -0.01em;
}

.card-14__desc {
  font-size: 12.5px;
  color: #6b6459;
  margin: 0 0 12px;
  line-height: 1.55;
}

.card-14__tags {
  list-style: none;
  padding: 0;
  margin: 0 0 14px;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.card-14__tags li {
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 4px;
  background: #f5f2ec;
  color: #6b6459;
}

.card-14__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 12px;
  border-top: 1px solid rgba(0,0,0,0.06);
}

.card-14__client {
  font-size: 12px;
  color: #6b6459;
  font-weight: 600;
}

.card-14__link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 44px;
  font-size: 12.5px;
  font-weight: 600;
  color: #b8613b;
  text-decoration: none;
}

.card-14__link:hover span {
  transform: translateX(3px);
}

.card-14__link span {
  display: inline-block;
  transition: transform 0.2s;
}

.card-14__link:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .card-14__card,
    .card-14__mock,
    .card-14__overlay,
    .card-14__link span {
    transition: none;
  }

  .card-14__card:hover {
    transform: none;
  }

  .card-14__card:hover .card-14__mock {
    transform: none;
  }

  .card-14__link:hover span {
    transform: none;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Card from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Portfolio Project Card
Source: https://codefronts.com/components/css-cards/portfolio-project-card/

Portfolio tile with a CSS-drawn dashboard mockup (top bar, side nav, KPI row, bar chart), category chip, project title, description, tags, and a client + "View project →" footer.
## HTML
```html
<section class="card-14">
  <article class="card-14__card">
    <div class="card-14__thumb">
      <div class="card-14__mock" aria-hidden="true">
        <div class="card-14__mock-top"></div>
        <div class="card-14__mock-body">
          <div class="card-14__mock-side">
            <span></span><span></span><span></span><span></span>
          </div>
          <div class="card-14__mock-main">
            <div class="card-14__mock-kpi"><span></span><span></span><span></span></div>
            <div class="card-14__mock-chart">
              <span style="height:35%"></span>
              <span style="height:55%"></span>
              <span style="height:40%"></span>
              <span style="height:70%"></span>
              <span style="height:60%"></span>
              <span style="height:85%"></span>
              <span style="height:50%"></span>
              <span style="height:75%"></span>
            </div>
          </div>
        </div>
      </div>
      <div class="card-14__overlay" aria-hidden="true"></div>
    </div>
    <div class="card-14__body">
      <span class="card-14__chip">SAAS &middot; DASHBOARD</span>
      <h3 class="card-14__title">Meridian Analytics Rebrand</h3>
      <p class="card-14__desc">Redesigned the analytics dashboard for 12k+ enterprise users — 41% faster time-to-insight.</p>
      <ul class="card-14__tags">
        <li>Figma</li>
        <li>Design System</li>
        <li>Data Viz</li>
      </ul>
      <div class="card-14__foot">
        <span class="card-14__client">Meridian Labs</span>
        <a class="card-14__link" href="#project">View project <span aria-hidden="true">→</span></a>
      </div>
    </div>
  </article>
</section>
```
## CSS
```css
.card-14 {
  width: 100%;
  min-height: 100vh;
  display: block;
  padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 40px);
  box-sizing: border-box;
  background: #f8f7f4;
  font-family: 'Inter', system-ui, sans-serif;
  --focus: #b8613b;
}

.card-14 *,
.card-14 *::before,
.card-14 *::after {
  box-sizing: border-box;
}

.card-14__card {
  width: 100%;
  max-width: 340px;
  margin-left: auto;
  margin-right: auto;
  background: #fff;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid rgba(0,0,0,0.06);
  box-shadow: 0 2px 12px rgba(0,0,0,0.04);
  transition: transform 0.25s, box-shadow 0.25s;
}

.card-14__card:hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 32px rgba(184,97,59,0.14);
}

.card-14__thumb {
  position: relative;
  aspect-ratio: 16/9;
  background: linear-gradient(135deg, #f7edd6, #e8c9a0);
  overflow: hidden;
}

.card-14__mock {
  position: absolute;
  inset: 14px;
  background: #fff;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(60,30,10,0.15);
  transition: transform 0.35s ease;
}

.card-14__card:hover .card-14__mock {
  transform: scale(1.04);
}

.card-14__mock-top {
  height: 12px;
  background: #f0eae1;
  border-bottom: 1px solid rgba(0,0,0,0.06);
}

.card-14__mock-body {
  display: grid;
  grid-template-columns: 40px 1fr;
  height: calc(100% - 12px);
}

.card-14__mock-side {
  background: #faf7f2;
  padding: 8px 6px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.card-14__mock-side span {
  display: block;
  height: 5px;
  border-radius: 2px;
  background: #e0d5c4;
}

.card-14__mock-side span:first-child {
  background: #b8613b;
}

.card-14__mock-main {
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.card-14__mock-kpi {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
}

.card-14__mock-kpi span {
  height: 20px;
  background: #f0eae1;
  border-radius: 3px;
}

.card-14__mock-chart {
  flex: 1;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  padding-top: 4px;
}

.card-14__mock-chart span {
  flex: 1;
  background: linear-gradient(180deg, #d97e4a, #b8451f);
  border-radius: 2px 2px 0 0;
}

.card-14__overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.05));
  transition: opacity 0.3s;
}

.card-14__card:hover .card-14__overlay {
  opacity: 0;
}

.card-14__body {
  padding: 16px 18px 18px;
}

.card-14__chip {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: #b8613b;
  background: rgba(184,97,59,0.1);
  padding: 4px 9px;
  border-radius: 999px;
  margin-bottom: 10px;
}

.card-14__title {
  font-size: 16px;
  font-weight: 700;
  margin: 0 0 6px;
  color: #1a1a1a;
  letter-spacing: -0.01em;
}

.card-14__desc {
  font-size: 12.5px;
  color: #6b6459;
  margin: 0 0 12px;
  line-height: 1.55;
}

.card-14__tags {
  list-style: none;
  padding: 0;
  margin: 0 0 14px;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.card-14__tags li {
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 4px;
  background: #f5f2ec;
  color: #6b6459;
}

.card-14__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 12px;
  border-top: 1px solid rgba(0,0,0,0.06);
}

.card-14__client {
  font-size: 12px;
  color: #6b6459;
  font-weight: 600;
}

.card-14__link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 44px;
  font-size: 12.5px;
  font-weight: 600;
  color: #b8613b;
  text-decoration: none;
}

.card-14__link:hover span {
  transform: translateX(3px);
}

.card-14__link span {
  display: inline-block;
  transition: transform 0.2s;
}

.card-14__link:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .card-14__card,
    .card-14__mock,
    .card-14__overlay,
    .card-14__link span {
    transition: none;
  }

  .card-14__card:hover {
    transform: none;
  }

  .card-14__card:hover .card-14__mock {
    transform: none;
  }

  .card-14__link:hover span {
    transform: none;
  }
}
```

How this works

This portfolio project card is a 340px article with a 16px border-radius, a 16:9 CSS-drawn dashboard thumbnail sitting above a body block with a chip, title, description, tag row, client name, and view-project arrow-link. The thumbnail paints a warm linear-gradient(135deg, #f7edd6, #e8c9a0) as its background wash, then insets a 14px white mock rectangle on top — the mock is a mini analytics dashboard drawn entirely in flex and grid with no images.

The mock is a grid of grid-template-columns: 40px 1fr: a left rail rendered as four horizontal bars (the first painted #b8613b for the active-state hint), and a main area with a 3-column KPI row followed by an 8-bar chart. Each chart bar is a span with an inline style height between 35% and 85%, and every bar carries a linear-gradient(180deg, #d97e4a, #b8451f) fill with border-radius: 2px 2px 0 0. The whole mock scales to transform: scale(1.04) on card-hover while a rgba(0,0,0,0.05) overlay fades to transparent — two coordinated micro-motions that read as a project preview.

The body follows a strict Read.cv-style pattern: a 10px monospaced-tracking chip (letter-spacing: 0.14em) painted at color: #b8613b on rgba(184,97,59,0.1), a 16px title, a 12.5px description, three tag pills, and a footer that separates client name from the View project arrow-link with a top border. The arrow slides translateX(3px) on link-hover, the anchor keeps a 44px min-height for WCAG 2.5.5, and every transition collapses under prefers-reduced-motion.

Make it yours

  • Retint the entire palette by swapping the --focus token from #b8613b to your brand hue and updating the three rgba(184, 97, 59, ...) references on the chip, hover shadow, and link — the mock chart gradient d97e4a-to-b8451f stays independent so the thumbnail keeps its warm signature.
  • Replace the CSS-drawn dashboard mock with a real screenshot by dropping an img tag at aspect-ratio 16/9 inside the thumb div; keep object-fit: cover on the img and the 14px inset white padding disappears cleanly for photo-based portfolios.
  • Reflow tags into overflow rows by keeping flex-wrap: wrap on the tags list — the 6px gap and 4px border-radius stay stable at any count, and long project stacks like Figma, Design System, Data Viz, Motion, Prototyping wrap without container-query gymnastics.
  • Turn the whole card into an anchor by wrapping the article in an a tag with display: block; text-decoration: none — move the outline focus ring from the inner link up to the outer anchor, and the entire tile becomes one keyboard target with the arrow still animating on hover.
  • Compose a portfolio grid by parent-setting grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)) and gap: 24px — the 340px card cap plus 16:9 thumbnail produces the same visual rhythm Cargo and Read.cv portfolios ship on their case-study pages.

Gotchas — read before shipping

  • The chip color #b8613b on rgba(184,97,59,0.1) computed against #fff resolves to roughly 4.6:1 — clears WCAG 1.4.3 AA at the boundary. Any darker background (light-tan surface, off-white paper) drops the effective ratio below 4.5:1. Always test the chip against the parent grid page background, not against #fff in isolation.
  • The mock chart uses inline style attributes for heights on span children — React strict JSX will complain about style objects if you paste this into a component. Convert every span to style={{ height: '35%' }} or store the eight heights in an array and map — never leave string-style inline attributes in a JSX file.
  • The transform: scale(1.04) on the inner mock during card-hover is composited on top of the border-radius clip — Safari occasionally leaks pixels outside the 6px rounded corner during the scale keyframe. Add will-change: transform on the mock, or drop the scale to 1.02 to keep the seam clean.
  • The View project link uses a right arrow character followed by aria-hidden="true" — screen readers do not hear the arrow, which is correct WCAG 4.1.2. Never expose the arrow as content: assistive users already know the link goes somewhere from the anchor role.
  • The 44px min-height on the anchor exists purely to hit WCAG 2.5.5 — if you tighten padding on the footer to fit a denser grid, keep the min-height even if visible content is only 20px tall. The touch target size regulation is about the hit area, not the visible glyph.
  • The card-hover animation runs on the card, mock, and overlay simultaneously — three concurrent property transitions per card. On a 30-card portfolio index page this saturates the compositor thread on 60Hz Android and drops INP well past 200ms. Use content-visibility: auto on the outer card so offscreen cards skip the transition graph entirely.

Techniques used in this demo

Search CodeFronts

Loading…