v135 · css

Vertical article navigator

::scroll-button(up) and ::scroll-button(down) turn a snap-scroll article into a paginated reader with zero JavaScript. The buttons auto-disable at the top and bottom edges and announce their direction to screen readers — all from CSS.

::scroll-button(): checking…
Before: JS-wired buttons
Two <button> elements outside the scroller, click listeners calling scrollBy(), manual disabled-state management at the top and bottom, and separate ARIA labels for each direction.
After: ::scroll-button(up/down)
Zero HTML. Zero JavaScript. Two CSS rules: ::scroll-button(up) and ::scroll-button(down). The browser generates focusable buttons, handles :disabled at the edges, and announces direction to AT.
Button style
Section 1 of 5
Introduction to CSS Scroll Buttons

The ::scroll-button() pseudo-element creates native interactive scroll controls directly inside a scroll container. Unlike manually created buttons outside the scroller, these are generated by the browser and automatically integrate with the container's overflow and snap state.

Scroll containers can now have up to four directional buttons — inline-start, inline-end, block-start, and block-end — or their physical aliases (left, right, up, down).

This article uses ↑ prev section and ↓ next section scroll buttons to navigate between snap-aligned sections.

Section 2 of 5
How They Work

Each ::scroll-button() is a pseudo-element on the scroll container itself. The browser places them at the leading or trailing edge of the scrollable area, inside the container's layout but outside the scrollable content.

The content property sets the button label — any CSS content value works, including emoji, counter(), and attr(). The button is focusable by default and participates in tab order.

When the scroll container is at the start, ::scroll-button(up) and ::scroll-button(left) become :disabled. When at the end, ::scroll-button(down) and ::scroll-button(right) become :disabled.

Section 3 of 5
Accessibility

The generated buttons are focusable with Tab and activatable with Enter or Space. The browser announces them as interactive controls to assistive technology.

The :disabled state is reflected in accessibility APIs — screen readers know when a directional button can't do anything and skip or announce it appropriately.

Before this feature, authors had to manually manage aria-disabled, update it on scroll, and ensure button placement was correct in the document order relative to the scroll container.

Section 4 of 5
Styling Options

Like any pseudo-element, ::scroll-button() accepts full CSS styling. The buttons on this article can be styled in four modes using the controls above:

  • Default — text label, mono font, subtle stone background
  • Arrow only — just ↑ / ↓, compact height, centered
  • Pill — rounded, elevated, drop-shadow effect
  • Minimal — border only, transparent background

The :disabled pseudo-class lets you dim buttons at the scroll edges without any JavaScript state management.

Section 5 of 5
Browser Support and Fallback

::scroll-button() shipped in Chrome 135. Browsers that don't support it simply show no button — the scroll container still scrolls manually with mouse, touch, or keyboard.

For critical navigation, pair with a JavaScript fallback that injects real <button> elements and wires up scrollBy(). Feature detection:

const supported = CSS.supports('selector(::scroll-button(up))');

Use the feature detection status banner at the top of this page to see if your browser supports it natively.

/* Vertical article reader with CSS-only navigation */
.article-scroller {
  height: 380px;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
}

.article-scroller::scroll-button(up) {
  content: "↑ prev section";
  display: flex;
  align-items: center;
  justify-content: center;
  height: 2.5rem;
  background: var(--bg-stone);
  font-family: monospace;
  font-size: 0.65rem;
  font-weight: 700;
  border-bottom: 1px solid;
  cursor: pointer;
}

.article-scroller::scroll-button(down) {
  content: "↓ next section";
  /* mirror of up button */
  border-top: 1px solid;
}

.article-scroller::scroll-button(up):disabled,
.article-scroller::scroll-button(down):disabled {
  opacity: 0.4;  /* auto-disabled at scroll edges */
  cursor: default;
}

.article-section {
  scroll-snap-align: start;
  min-height: 380px;  /* each section fills the viewport */
}

see also

references