UI Components Medium
Scroll Area
Custom-styled scrollable container with thin styled scrollbar, auto-hide on idle, and horizontal + vertical variants.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg: #050910;
--card: #0d1117;
--border: rgba(255, 255, 255, 0.08);
--text: #f2f6ff;
--muted: #475569;
--accent: #38bdf8;
--sb-thumb: rgba(255, 255, 255, 0.12);
--sb-thumb-hover: rgba(255, 255, 255, 0.28);
--radius: 12px;
}
body {
background: var(--bg);
color: var(--text);
font-family: system-ui, -apple-system, sans-serif;
min-height: 100vh;
padding: 3rem 1.5rem;
}
.page {
max-width: 720px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 2.5rem;
}
.demo-section {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.demo-label {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
}
/* โโ Scroll area base โโ */
.scroll-area {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
outline: none;
position: relative;
}
.scroll-area:focus-visible {
box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.35);
}
/* โโ Webkit scrollbar โ shared โโ */
.scroll-area::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.scroll-area::-webkit-scrollbar-track {
background: transparent;
}
.scroll-area::-webkit-scrollbar-corner {
background: transparent;
}
.scroll-area::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 3px;
transition: background 0.25s;
}
/* Show thumb only while scrolling */
.scroll-area.scrolling::-webkit-scrollbar-thumb {
background: var(--sb-thumb);
}
.scroll-area::-webkit-scrollbar-thumb:hover {
background: var(--sb-thumb-hover);
}
/* Firefox */
.scroll-area {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
.scroll-area.scrolling {
scrollbar-color: var(--sb-thumb) transparent;
}
/* โโ Vertical โโ */
.scroll-area--vertical {
height: 280px;
overflow-y: auto;
overflow-x: hidden;
}
/* โโ Horizontal โโ */
.scroll-area--horizontal {
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 0.5rem; /* space for scrollbar */
}
/* โโ Both โโ */
.scroll-area--both {
height: 260px;
overflow: auto;
}
/* โโ Item list (vertical) โโ */
.item-list {
list-style: none;
padding: 0.5rem;
}
.item {
display: flex;
align-items: center;
gap: 0.875rem;
padding: 0.625rem 0.75rem;
border-radius: 8px;
font-size: 0.875rem;
transition: background 0.15s;
}
.item:hover {
background: rgba(255, 255, 255, 0.03);
}
.item-num {
font-size: 0.72rem;
font-weight: 600;
color: var(--muted);
font-variant-numeric: tabular-nums;
width: 22px;
flex-shrink: 0;
}
/* โโ Horizontal cards โโ */
.card-row {
display: flex;
gap: 0.75rem;
padding: 0.75rem;
width: max-content;
}
.h-card {
width: 120px;
height: 100px;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-end;
padding: 0.875rem;
flex-shrink: 0;
position: relative;
overflow: hidden;
}
.h-card--1 {
background: linear-gradient(135deg, #0f2044, #0a3a5c);
}
.h-card--2 {
background: linear-gradient(135deg, #1a0a3c, #3b0f6e);
}
.h-card--3 {
background: linear-gradient(135deg, #072a1e, #0d4a32);
}
.h-card--4 {
background: linear-gradient(135deg, #2a0f0a, #5c1f10);
}
.h-card--5 {
background: linear-gradient(135deg, #1a1a0a, #3d3800);
}
.h-card--6 {
background: linear-gradient(135deg, #0a1f2a, #0d3650);
}
.h-card--7 {
background: linear-gradient(135deg, #1f0a2a, #450f5e);
}
.h-card--8 {
background: linear-gradient(135deg, #0a2a10, #0d4818);
}
.h-card--9 {
background: linear-gradient(135deg, #2a1a0a, #5c3a10);
}
.h-card--10 {
background: linear-gradient(135deg, #001f2a, #003655);
}
.h-card__num {
position: absolute;
top: 0.625rem;
right: 0.75rem;
font-size: 0.68rem;
color: rgba(255, 255, 255, 0.3);
font-variant-numeric: tabular-nums;
}
.h-card__title {
font-size: 0.85rem;
font-weight: 700;
color: rgba(255, 255, 255, 0.9);
letter-spacing: -0.01em;
}
/* โโ Both-axes grid โโ */
.both-content {
padding: 0.75rem;
width: max-content;
}
.both-grid {
display: grid;
grid-template-columns: repeat(10, 80px);
grid-template-rows: repeat(8, 60px);
gap: 6px;
}
.grid-cell {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border);
border-radius: 6px;
display: grid;
place-items: center;
font-size: 0.7rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
transition: background 0.15s, border-color 0.15s;
}
.grid-cell:hover {
background: rgba(56, 189, 248, 0.06);
border-color: rgba(56, 189, 248, 0.2);
color: var(--text);
}(function () {
"use strict";
// โโ Auto-hide scrollbar โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.querySelectorAll(".scroll-area").forEach(function (el) {
let timer = null;
el.addEventListener(
"scroll",
function () {
el.classList.add("scrolling");
clearTimeout(timer);
timer = setTimeout(function () {
el.classList.remove("scrolling");
}, 1000);
},
{ passive: true }
);
});
// โโ Build both-axes grid โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const grid = document.querySelector(".both-grid");
if (grid) {
for (let row = 1; row <= 8; row++) {
for (let col = 1; col <= 10; col++) {
const cell = document.createElement("div");
cell.className = "grid-cell";
cell.textContent = row + "ยท" + col;
grid.appendChild(cell);
}
}
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Scroll Area</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="page">
<!-- โโ Demo 1: Vertical scroll โโ -->
<div class="demo-section">
<h2 class="demo-label">Vertical scroll area</h2>
<div class="scroll-area scroll-area--vertical" tabindex="0" aria-label="Vertical scroll area">
<ul class="item-list">
<li class="item"><span class="item-num">01</span><span>Accordion Spring</span></li>
<li class="item"><span class="item-num">02</span><span>Animated Tabs</span></li>
<li class="item"><span class="item-num">03</span><span>Carousel</span></li>
<li class="item"><span class="item-num">04</span><span>Command Palette</span></li>
<li class="item"><span class="item-num">05</span><span>Context Menu</span></li>
<li class="item"><span class="item-num">06</span><span>Data Table</span></li>
<li class="item"><span class="item-num">07</span><span>Date Picker</span></li>
<li class="item"><span class="item-num">08</span><span>Drag to Reorder</span></li>
<li class="item"><span class="item-num">09</span><span>Flip Card 3D</span></li>
<li class="item"><span class="item-num">10</span><span>Glass Card</span></li>
<li class="item"><span class="item-num">11</span><span>Glow Metric Card</span></li>
<li class="item"><span class="item-num">12</span><span>Infinite Marquee</span></li>
<li class="item"><span class="item-num">13</span><span>Navigation Menu</span></li>
<li class="item"><span class="item-num">14</span><span>OTP Input</span></li>
<li class="item"><span class="item-num">15</span><span>Resizable Panels</span></li>
<li class="item"><span class="item-num">16</span><span>Scroll Area</span></li>
<li class="item"><span class="item-num">17</span><span>Sheet Drawer</span></li>
<li class="item"><span class="item-num">18</span><span>Skeleton Loader</span></li>
<li class="item"><span class="item-num">19</span><span>Toast System</span></li>
<li class="item"><span class="item-num">20</span><span>Toggle Switch</span></li>
</ul>
</div>
</div>
<!-- โโ Demo 2: Horizontal scroll โโ -->
<div class="demo-section">
<h2 class="demo-label">Horizontal scroll area</h2>
<div class="scroll-area scroll-area--horizontal" tabindex="0" aria-label="Horizontal scroll area">
<div class="card-row">
<div class="h-card h-card--1"><span class="h-card__num">01</span><span class="h-card__title">Design</span></div>
<div class="h-card h-card--2"><span class="h-card__num">02</span><span class="h-card__title">Motion</span></div>
<div class="h-card h-card--3"><span class="h-card__num">03</span><span class="h-card__title">Code</span></div>
<div class="h-card h-card--4"><span class="h-card__num">04</span><span class="h-card__title">Systems</span></div>
<div class="h-card h-card--5"><span class="h-card__num">05</span><span class="h-card__title">API</span></div>
<div class="h-card h-card--6"><span class="h-card__num">06</span><span class="h-card__title">UX</span></div>
<div class="h-card h-card--7"><span class="h-card__num">07</span><span class="h-card__title">Perf</span></div>
<div class="h-card h-card--8"><span class="h-card__num">08</span><span class="h-card__title">A11y</span></div>
<div class="h-card h-card--9"><span class="h-card__num">09</span><span class="h-card__title">DX</span></div>
<div class="h-card h-card--10"><span class="h-card__num">10</span><span class="h-card__title">Craft</span></div>
</div>
</div>
</div>
<!-- โโ Demo 3: Both axes โโ -->
<div class="demo-section">
<h2 class="demo-label">Both axes</h2>
<div class="scroll-area scroll-area--both" tabindex="0" aria-label="Scrollable in both directions">
<div class="both-content">
<div class="both-grid">
<!-- 8x8 grid of cells -->
</div>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Scroll Area
Custom-styled scrollable containers with thin, themed scrollbars that auto-hide when idle. Three layout variants: vertical, horizontal, and both axes.
Variants
- Vertical โ fixed-height list with 20 items, thin right scrollbar
- Horizontal โ row of wide cards with bottom scrollbar
- Both axes โ free-scroll content area with scrollbars on both sides
How it works
::-webkit-scrollbarresets the default browser scrollbar to 6px width/height- The thumb uses
rgba(255,255,255,0.15)at rest andrgba(255,255,255,0.3)on hover - A JS scroll listener adds
.scrollingto the container; a 1 s debounced timeout removes it - CSS transitions on
scrollbar-colorand thumb opacity create a smooth fade-in/out - Firefox uses
scrollbar-width: thinandscrollbar-colorfor cross-browser support
CSS-only fallback
The auto-hide behaviour requires JS, but the styled scrollbar is pure CSS. Removing the script degrades gracefully โ the scrollbar stays visible but still looks correct.