Theme
On this page (7)
Component Skill — components/blog-item/component-skill.md

Native basis

An <article> - image, category (Badge) + read time, the title link stretched over the item, a clamped excerpt and the byline (Avatar, name, <time>).

Built from

Badge, Avatar, Swap.

Web Platform APIs

<article>line-clampaspect-ratioscale

Classes

.mk-blog-item.mk-blog-item-title.mk-blog-item-media.mk-blog-item-body.mk-blog-item-meta.mk-blog-item-excerpt.mk-blog-item-byline.mk-blog-item-author.mk-blog-item-date.mk-blog-item-tags.mk-blog-item-actions.mk-blog-item-player

Data attributes

data-variant (horizontal, featured); data-variant (card, minimal, quote, podcast); .mk-blog-item-tags, .mk-blog-item-actions (a bookmark Swap), .mk-blog-item-player.

§Default

Three cards in a grid. Each is one link (the title's); the category is a Badge, the byline an Avatar.

§Horizontal

data-variant='horizontal': the image beside the copy - narrow it and the row wraps, image on top (no media query).

data-variant='featured': the copy laid over the image on a dark gradient - the top post of a blog front.

§Cards with tags and bookmarks

data-variant='card': a card surface that lifts on hover. Tag Badges and a bookmark Swap (top right of the image) sit above the stretched link - click them without opening the post.

§Minimal

data-variant='minimal': no image - the date in its own column, the title large. An archive or an essay index.

§Quote and podcast

data-variant='quote' leads with a pull quote on the primary surface; data-variant='podcast' sets a square cover beside the copy with an <audio> player (above the stretched link) below.

§CSS view file

/* -- Blog Item (website) --------------------------------------------
   Previews one post: image, category + read time, the title (its link
   stretches over the whole item), a clamped excerpt and the byline (Avatar).
   Default: a vertical card; horizontal: image beside the copy; featured:
   the copy laid over the image. */
@layer components {
  .mk-blog-item {
    container: mk-blog-item / inline-size;
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 0;
    color: var(--foreground);
    &:has(.mk-blog-item-title a:focus-visible) {
      outline: 2px solid var(--ring);
      outline-offset: 4px;
      border-radius: var(--radius-lg);
    }
  }
  .mk-blog-item-media {
    aspect-ratio: 3 / 2;
    margin: 0;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--muted);
    & img {
      display: block;
      width: 100%;
      height: 100%;
      object-fit: cover;
      transition: scale 300ms ease;
    }
  }
  .mk-blog-item:hover .mk-blog-item-media img { scale: 1.03; }
  .mk-blog-item-body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
  }
  .mk-blog-item-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    color: var(--muted-foreground);
    font-size: 0.8125rem;
  }
  .mk-blog-item-title {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: -0.02em;
    text-wrap: balance;
    & a {
      color: inherit;
      text-decoration: none;
      outline: none;
      &::after { content: ""; position: absolute; inset: 0; }
    }
  }
  .mk-blog-item-excerpt {
    margin: 0;
    color: var(--muted-foreground);
    font-size: 0.9375rem;
    line-height: 1.6;
    text-wrap: pretty;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .mk-blog-item-byline {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    margin-block-start: auto;
    padding-block-start: 0.5rem;
    font-size: 0.8125rem;
  }
  .mk-blog-item-author { font-weight: 500; }
  .mk-blog-item-date { color: var(--muted-foreground); }
  /* horizontal: image beside the copy - the row wraps (image above) once
     the copy would drop under 18rem; no query needed */
  .mk-blog-item[data-variant="horizontal"] {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 1rem 1.5rem;
    & .mk-blog-item-media { flex: 1 1 14rem; max-width: 100%; }
    & .mk-blog-item-body { flex: 999 1 18rem; }
  }
  /* featured: the copy over the image, on a darkening gradient */
  .mk-blog-item[data-variant="featured"] {
    justify-content: flex-end;
    isolation: isolate;
    min-height: 24rem;
    padding: 1.5rem;
    overflow: hidden;
    border-radius: var(--radius-xl);
    background: #0b0b0f;
    color: #fff;
    & .mk-blog-item-media {
      position: absolute;
      inset: 0;
      z-index: -1;
      aspect-ratio: auto;
      border-radius: 0;
      &::after {
        content: "";
        position: absolute;
        inset: 0;
        background: linear-gradient(to top, rgb(0 0 0 / 0.85), rgb(0 0 0 / 0.35) 55%, transparent);
      }
    }
    & .mk-blog-item-body { flex: none; max-width: 40rem; }
    & .mk-blog-item-title { font-size: 1.75rem; }
    & :is(.mk-blog-item-meta, .mk-blog-item-excerpt, .mk-blog-item-date) { color: rgb(255 255 255 / 0.8); }
  }
  @container mk-blog-item (min-width: 40rem) {
    .mk-blog-item[data-variant="featured"] .mk-blog-item-title { font-size: 2.25rem; }
  }
  /* tags under the excerpt, actions on the image (a bookmark Swap…): raised
     above the stretched link so they stay clickable */
  .mk-blog-item-tags {
    position: relative;
    z-index: 1;
    & a { text-decoration: none; }
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
  }
  .mk-blog-item-actions {
    position: absolute;
    inset-block-start: 0.625rem;
    inset-inline-end: 0.625rem;
    z-index: 1;
    display: flex;
    gap: 0.25rem;
    & .swap,
    & .btn {
      border-radius: 999px;
      background: color-mix(in oklch, var(--background) 85%, transparent);
      backdrop-filter: blur(6px);
      box-shadow: var(--shadow-sm);
    }
  }
  /* card: on a card surface, lifted on hover */
  .mk-blog-item[data-variant="card"] {
    gap: 0;
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    background: var(--card);
    transition: translate 200ms ease, box-shadow 200ms ease;
    &:hover { translate: 0 -3px; box-shadow: var(--shadow-lg); }
    & .mk-blog-item-media { border-radius: 0; }
    & .mk-blog-item-body { padding: 1.125rem 1.25rem 1.25rem; }
    & .mk-blog-item-byline { padding-block-start: 0.75rem; margin-block-start: auto; border-block-start: 1px solid var(--border); }
  }
  /* minimal: typographic - the date in a column, no image */
  .mk-blog-item[data-variant="minimal"] {
    display: grid;
    grid-template-columns: 6.5rem minmax(0, 1fr);
    gap: 0 1.5rem;
    padding-block: 1.25rem;
    border-block-end: 1px solid var(--border);
    & .mk-blog-item-media { display: none; }
    & .mk-blog-item-body { display: contents; }
    & .mk-blog-item-byline { grid-column: 1; grid-row: 1 / span 3; flex-direction: column; align-items: flex-start; gap: 0.125rem; padding: 0.25rem 0 0; margin: 0; }
    & :is(.mk-blog-item-byline .avatar, .mk-blog-item-author) { display: none; }
    & :is(.mk-blog-item-meta, .mk-blog-item-title, .mk-blog-item-excerpt, .mk-blog-item-tags) { grid-column: 2; }
    & .mk-blog-item-title { font-size: 1.375rem; }
    &:hover .mk-blog-item-title { text-decoration: underline; text-underline-offset: 0.2em; }
  }
  /* quote: a post that leads with a pull quote */
  .mk-blog-item[data-variant="quote"] {
    justify-content: space-between;
    min-height: 16rem;
    padding: 1.5rem;
    border-radius: var(--radius-xl);
    background: var(--primary);
    color: var(--primary-foreground);
    & .mk-blog-item-media { display: none; }
    & .mk-blog-item-title {
      font-family: var(--font-serif);
      font-size: 1.5rem;
      font-weight: 600;
      line-height: 1.3;
      &::before { content: "“"; display: block; font-size: 3rem; line-height: 0.6; opacity: 0.5; }
    }
    & :is(.mk-blog-item-meta, .mk-blog-item-excerpt, .mk-blog-item-date) { color: color-mix(in oklch, var(--primary-foreground) 75%, transparent); }
    & .mk-blog-item-meta .badge { background: color-mix(in oklch, var(--primary-foreground) 15%, transparent); color: inherit; }
  }
  /* podcast: a square cover beside the copy, a player under it */
  .mk-blog-item[data-variant="podcast"] {
    display: grid;
    grid-template-columns: 7rem minmax(0, 1fr);
    gap: 0 1.25rem;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    background: var(--card);
    & .mk-blog-item-media { aspect-ratio: 1; border-radius: var(--radius-md); }
    & .mk-blog-item-excerpt { -webkit-line-clamp: 2; }
    & .mk-blog-item-byline { padding-block-start: 0.25rem; }
  }
  .mk-blog-item-player {
    position: relative;
    z-index: 1;
    grid-column: 1 / -1;
    width: 100%;
    height: 2.5rem;
    margin-block-start: 0.875rem;
  }
  /* -- Accessibility ------------------------------------------- */
  @media (prefers-reduced-motion: reduce) {
    .mk-blog-item-media img { transition: none; }
    .mk-blog-item:hover .mk-blog-item-media img { scale: none; }
    .mk-blog-item[data-variant="card"] { transition: none; }
    .mk-blog-item[data-variant="card"]:hover { translate: none; }
  }
  @media (prefers-contrast: more) {
    .mk-blog-item-excerpt,
    .mk-blog-item-meta { color: var(--foreground); }
  }
  @media (forced-colors: active) {
    .mk-blog-item-title a { color: LinkText; }
    .mk-blog-item[data-variant="featured"] { border: 1px solid CanvasText; }
  }
}

Comments, ideas or improvements? Edit this page's source on GitHub