Theme
Design your own
On this page (3)
Component Skill — components/playlist-item/component-skill.md

Native basis

An <li> in an <ol class="mk-playlist"> holding a <label> around a radio input: the index (replaced by animated bars when selected), a cover, title and artist, and the duration.

Web Platform APIs

:has()<ol>prefers-reduced-motion

Classes

.mk-playlist.mk-playlist-item.mk-playlist-item-index.mk-playlist-item-bars.mk-playlist-item-text.mk-playlist-item-cover.mk-playlist-item-desc.mk-playlist-item-duration

Data attributes

data-variant="episode".

§Tracks

Each track is a radio input in its label - click a track or use the arrow keys; :has(:checked) highlights it and swaps the number for animated "now playing" bars (still under reduced motion).

§Episodes

data-variant='episode': a larger cover, the date and a two-line description.

§CSS view file

/* -- Playlist Item (website · Media) --------------------------------------
   Represents one selectable track or episode within a playlist - number, cover, title, artist and length; the selected one shows "now playing" bars. */
@layer components {
  /* every box in the block measures border-box - the same on any page */
  :where(.mk-playlist-item, .mk-playlist-item *, .mk-playlist-item ::before, .mk-playlist-item ::after) {
    box-sizing: border-box;
  }
  .mk-playlist {
    display: grid;
    gap: 0.125rem;
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .mk-playlist-item label {
    position: relative;
    display: grid;
    grid-template-columns: 1.75rem 2.75rem minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.875rem;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius-lg);
    cursor: pointer;
    transition: background-color 150ms ease;
    &:hover {
      background: var(--accent);
    }
    & input {
      position: absolute;
      inset: 0;
      margin: 0;
      opacity: 0;
      cursor: pointer;
    }
  }
  .mk-playlist-item:has(input:checked) label {
    background: color-mix(in oklch, var(--primary) 9%, transparent);
  }
  .mk-playlist-item:has(input:focus-visible) label {
    outline: 2px solid var(--ring);
    outline-offset: -2px;
  }
  .mk-playlist-item-index {
    display: grid;
    place-items: center;
    color: var(--muted-foreground);
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
  }
  .mk-playlist-item-bars {
    display: none;
    align-items: flex-end;
    gap: 2px;
    height: 0.875rem;
    font-style: normal;
    & b {
      width: 3px;
      height: 100%;
      border-radius: 1px;
      background: var(--primary);
      transform-origin: bottom;
      animation: mk-playlist-bars 900ms ease-in-out infinite alternate;
    }
    & b:nth-child(2) {
      animation-delay: -300ms;
    }
    & b:nth-child(3) {
      animation-delay: -600ms;
    }
  }
  @keyframes mk-playlist-bars {
    from {
      scale: 1 0.25;
    }
    to {
      scale: 1 1;
    }
  }
  .mk-playlist-item:has(input:checked) {
    & .mk-playlist-item-index > span {
      display: none;
    }
    & .mk-playlist-item-bars {
      display: flex;
    }
    & .mk-playlist-item-text strong {
      color: var(--primary);
    }
  }
  .mk-playlist-item-cover {
    display: block;
    width: 2.75rem;
    height: 2.75rem;
    object-fit: cover;
    border-radius: var(--radius-md);
    background: var(--muted);
  }
  .mk-playlist-item-text {
    display: grid;
    min-width: 0;
    & strong {
      overflow: hidden;
      font-size: 0.9375rem;
      font-weight: 600;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    & small {
      overflow: hidden;
      color: var(--muted-foreground);
      font-size: 0.8125rem;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }
  .mk-playlist-item-desc {
    display: -webkit-box;
    margin-block-start: 0.25rem;
    overflow: hidden;
    color: var(--muted-foreground);
    font-size: 0.8125rem;
    line-height: 1.5;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }
  .mk-playlist-item-duration {
    color: var(--muted-foreground);
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
  }
  .mk-playlist-item[data-variant="episode"] label {
    grid-template-columns: 1.75rem 4.5rem minmax(0, 1fr) auto;
    align-items: start;
    padding-block: 0.75rem;
    & .mk-playlist-item-cover {
      width: 4.5rem;
      height: 4.5rem;
    }
    & .mk-playlist-item-index,
    & .mk-playlist-item-duration {
      padding-block-start: 0.25rem;
    }
  }
  /* -- Accessibility ------------------------------------------- */
  @media (prefers-reduced-motion: reduce) {
    .mk-playlist-item label {
      transition: none;
    }
    .mk-playlist-item-bars b {
      animation: none;
    }
    .mk-playlist-item-bars b:nth-child(1) {
      scale: 1 0.6;
    }
    .mk-playlist-item-bars b:nth-child(3) {
      scale: 1 0.8;
    }
  }
  @media (forced-colors: active) {
    .mk-playlist-item:has(input:checked) label {
      outline: 2px solid Highlight;
    }
    .mk-playlist-item-bars b {
      background: Highlight;
    }
  }
}

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