Theme
On this page (6)
Component Skill — components/news-header/component-skill.md

Native basis

A <header> with the title (h1), a <time> dateline and a <nav> of topic links - aria-current marks the current topic, the row scrolls on its own when it overflows.

Built from

Breadcrumb, Indicator, Input, Button, Toggle Group.

Web Platform APIs

<time>aria-currentoverscroll-behaviorContainer queries

Classes

.mk-news-header.mk-news-header-top.mk-news-header-kicker.mk-news-header-date.mk-news-header-title.mk-news-header-desc.mk-news-header-topics.mk-news-header-live.mk-news-header-tools

Data attributes

data-variant="masthead"; aria-current on the current topic link; data-variant="bar", data-wrap on the topics; .mk-news-header-live (an Indicator dot + the update time), .mk-news-header-tools.

§Default

Kicker and dateline, the title, a line of editorial context and the topics - the current one underlined (aria-current).

§Masthead

data-variant='masthead': a newspaper front - serif title, centered, between rules; the topics sit in a double-ruled band.

§Many topics: scroll or wrap

On a phone the topics do not fit. Top: the row scrolls - its end fades out under a › hint that is driven by the scroll itself and disappears once you reach the end (no script). Bottom: data-wrap on the topics breaks them onto lines instead.

§Category page, live

A Breadcrumb above the title, a live line with a pinging Indicator dot, the topic row below - a category page during breaking news.

§Bar

data-variant='bar': one compact row for an app or a sidebar-less site - the title, the topics, and tools: a search Input, a Toggle Group view switch and a subscribe Button.

§CSS view file

/* -- News Header (website) ------------------------------------------
   Introduces a news collection: kicker + dateline, the title, a line of
   editorial context and the topic links (the current one marked with
   aria-current). The masthead variant sets it like a newspaper front page. */
/* the overflow hint's strength (0-1), animated by the topic row's scroll */
@property --mk-news-header-more {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@layer components {
  .mk-news-header {
    container: mk-news-header / inline-size;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    box-sizing: border-box;
    width: 100%;
    max-width: 80rem;
    margin-inline: auto;
    padding-inline: 1.25rem;
  }
  .mk-news-header-top {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.25rem 1rem;
    font-size: 0.8125rem;
  }
  .mk-news-header-kicker {
    color: var(--primary);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  .mk-news-header-date { color: var(--muted-foreground); }
  .mk-news-header-title {
    margin: 0;
    font-size: 2.25rem;
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -0.03em;
    text-wrap: balance;
  }
  .mk-news-header-desc {
    margin: 0;
    max-width: 44rem;
    color: var(--muted-foreground);
    font-size: 1.0625rem;
    line-height: 1.55;
    text-wrap: pretty;
  }
  /* topics: one scrolling row, the current topic underlined */
  .mk-news-header-topics {
    display: flex;
    gap: 1.25rem;
    margin-block-start: 0.5rem;
    border-block-end: 1px solid var(--border);
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scrollbar-width: none;
    & a {
      flex: none;
      padding-block: 0.625rem;
      border-block-end: 2px solid transparent;
      margin-block-end: -1px;
      color: var(--muted-foreground);
      font-size: 0.875rem;
      font-weight: 500;
      text-decoration: none;
      white-space: nowrap;
      transition: color 120ms, border-color 120ms;
      &:hover { color: var(--foreground); }
      &[aria-current] { color: var(--foreground); border-block-end-color: var(--primary); }
      &:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; border-radius: var(--radius-sm); }
    }
  }
  /* more topics than fit: an overlay at the row's end - a fade into the
     background and a chevron. Its strength follows the row's own scroll (a
     scroll-driven animation): full while there is more to see, gone at the
     end; a row that fits has no active timeline, so no hint at all. The
     overlay takes no room (width = -margin) and sticks to the row's end. */
  .mk-news-header-topics {
    --mk-news-header-more: 0;
    animation: mk-news-header-more linear both;
    &::after {
      content: "›";
      position: sticky;
      inset-inline-end: 0;
      flex: none;
      width: 3rem;
      margin-inline-start: -3rem;
      padding-inline-end: 0.125rem;
      background: linear-gradient(to right, transparent, var(--background) 65%);
      color: var(--foreground);
      font-size: 1.25rem;
      line-height: 1;
      text-align: end;
      align-self: stretch;
      display: flex;
      align-items: center;
      justify-content: flex-end;
      opacity: var(--mk-news-header-more);
      pointer-events: none;
    }
  }
  @keyframes mk-news-header-more {
    from { --mk-news-header-more: 1; }
    to { --mk-news-header-more: 0; }
  }
  /* the timeline in its own rule: a minifier must not fold it into the
     `animation` shorthand (reset-only there - the declaration would be
     dropped). Unsupported: the animation runs once (0s) and ends at 0 - no
     hint, no harm */
  @supports (animation-timeline: scroll()) {
    .mk-news-header-topics { animation-timeline: scroll(self inline); }
    .mk-news-header-topics[data-wrap] { animation: none; }
  }
  /* data-wrap: break the topics onto more lines instead of scrolling */
  .mk-news-header-topics[data-wrap] {
    flex-wrap: wrap;
    column-gap: 1.25rem;
    overflow: visible;
    animation: none;
    &::after { content: none; }
  }
  /* masthead: a newspaper front - serif, centered, between double rules */
  .mk-news-header[data-variant="masthead"] {
    align-items: center;
    text-align: center;
    & .mk-news-header-top {
      width: 100%;
      padding-block: 0.375rem;
      border-block: 1px solid var(--foreground);
    }
    & .mk-news-header-title {
      font-family: var(--font-serif);
      font-weight: 800;
      font-size: 3rem;
      letter-spacing: -0.02em;
    }
    & .mk-news-header-topics {
      justify-content: safe center;
      width: 100%;
      border-block: 3px double var(--foreground);
      & a { padding-block: 0.5rem; border-block-end: 0; margin: 0; }
      & a[aria-current] { text-decoration: underline; text-underline-offset: 0.3em; text-decoration-color: var(--primary); }
    }
  }
  /* the live line: a pinging Indicator dot + "updated …" */
  .mk-news-header-live {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--muted-foreground);
    font-size: 0.8125rem;
    & strong { color: oklch(0.58 0.22 27); font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
  }
  /* tools on the right of the top line: search, view switch, subscribe */
  .mk-news-header-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
  }
  /* bar: one compact row - title, topics, tools */
  .mk-news-header[data-variant="bar"] {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1.5rem;
    padding-block: 0.75rem;
    border-block: 1px solid var(--border);
    & .mk-news-header-title { font-size: 1.375rem; letter-spacing: -0.02em; }
    & .mk-news-header-topics { flex: 1 1 8rem; min-width: 0; margin: 0; border: 0; }
    & .mk-news-header-topics a { padding-block: 0.25rem; }
    & .mk-news-header-tools { margin-inline-start: auto; }
  }
  @container mk-news-header (min-width: 48rem) {
    .mk-news-header[data-variant="bar"] .mk-news-header-title { font-size: 1.5rem; }
    .mk-news-header-title { font-size: 3rem; }
    .mk-news-header[data-variant="masthead"] .mk-news-header-title { font-size: 4.5rem; }
  }
  /* -- Accessibility ------------------------------------------- */
  @media (prefers-reduced-motion: reduce) {
    .mk-news-header-topics a { transition: none; }
  }
  @media (prefers-contrast: more) {
    .mk-news-header-desc,
    .mk-news-header-topics a { color: var(--foreground); }
  }
  @media (forced-colors: active) {
    .mk-news-header-topics a[aria-current] { border-block-end-color: Highlight; }
  }
}

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