DEV Community

ahmet gedik
ahmet gedik

Posted on

Building a Category Bar with Auto-Sizing CSS and JavaScript

A category navigation bar needs to handle any number of categories gracefully. Here's how I built an auto-sizing category bar for TrendVidStream that adapts from full labels to compact chips to minimal icons.

The Design Problem

The category bar shows trending video categories: Music, Gaming, Sports, Entertainment, etc. The number of active categories varies by region. On desktop, we want wrapped chips. On mobile, a horizontal scroll.

HTML Structure

<nav class="vw-catbar" id="catbar" aria-label="Video Categories">
    <a href="/category/music" class="chip active">Music</a>
    <a href="/category/gaming" class="chip">Gaming</a>
    <a href="/category/sports" class="chip">Sports</a>
    <a href="/category/entertainment" class="chip">Entertainment</a>
    <a href="/category/news" class="chip">News & Politics</a>
    <a href="/category/education" class="chip">Education</a>
    <a href="/category/science" class="chip">Science & Tech</a>
    <a href="/category/comedy" class="chip">Comedy</a>
    <a href="/category/howto" class="chip">How-to & Style</a>
</nav>
Enter fullscreen mode Exit fullscreen mode

CSS: 3 Size Tiers

/* Base styles */
.vw-catbar {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 12px 16px;
    background: #0f0f23;
    border-bottom: 1px solid #1a1a2e;
}

.chip {
    display: inline-flex;
    align-items: center;
    padding: 6px 14px;
    font-size: 0.9rem;
    font-weight: 500;
    color: #e0e0e0;
    background: #1a1a2e;
    border-radius: 20px;
    text-decoration: none;
    white-space: nowrap;
    transition: background 0.2s, color 0.2s;
}

.chip:hover {
    background: #2a2a4a;
    color: #ffffff;
}

.chip.active {
    background: #e50914;
    color: #ffffff;
}

/* Tier 2: Compact */
.vw-catbar-compact .chip {
    padding: 4px 10px;
    font-size: 0.82rem;
}

/* Tier 3: Mini */
.vw-catbar-mini .chip {
    padding: 3px 8px;
    font-size: 0.75rem;
    border-radius: 12px;
}

/* Mobile: horizontal scroll */
@media (max-width: 768px) {
    .vw-catbar {
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding: 8px 12px;
    }

    .vw-catbar::-webkit-scrollbar {
        display: none;
    }

    /* Reset compact/mini on mobile - let scroll handle it */
    .vw-catbar-compact .chip,
    .vw-catbar-mini .chip {
        padding: 6px 12px;
        font-size: 0.85rem;
    }
}
Enter fullscreen mode Exit fullscreen mode

JavaScript: Auto-Sizing

class CategoryBar {
    constructor(selector) {
        this.el = document.querySelector(selector);
        if (!this.el) return;

        this.fit();

        // Re-fit on resize with debounce
        let resizeTimer;
        window.addEventListener('resize', () => {
            clearTimeout(resizeTimer);
            resizeTimer = setTimeout(() => this.fit(), 150);
        });
    }

    fit() {
        // Skip on mobile (scroll handles it)
        if (window.innerWidth < 768) {
            this.el.classList.remove('vw-catbar-compact', 'vw-catbar-mini');
            return;
        }

        // Reset to normal size
        this.el.classList.remove('vw-catbar-compact', 'vw-catbar-mini');

        // Check if content overflows
        if (this.isOverflowing()) {
            // Try compact
            this.el.classList.add('vw-catbar-compact');

            if (this.isOverflowing()) {
                // Try mini
                this.el.classList.add('vw-catbar-mini');
            }
        }
    }

    isOverflowing() {
        return this.el.scrollHeight > this.el.clientHeight + 4;
    }
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    new CategoryBar('#catbar');
});
Enter fullscreen mode Exit fullscreen mode

CLS Prevention

The category bar must not cause layout shift during auto-sizing:

.vw-catbar {
    min-height: 48px; /* Reserve space */
    contain: layout; /* Prevent layout shifts from affecting page */
}
Enter fullscreen mode Exit fullscreen mode

PHP: Rendering Categories

<?php
function renderCatbar(array $categories, ?string $activeSlug = null): string
{
    $html = '<nav class="vw-catbar" id="catbar" aria-label="Video Categories">';

    foreach ($categories as $cat) {
        $active = ($cat['slug'] === $activeSlug) ? ' active' : '';
        $html .= sprintf(
            '<a href="/category/%s" class="chip%s">%s</a>',
            htmlspecialchars($cat['slug']),
            $active,
            htmlspecialchars($cat['name'])
        );
    }

    $html .= '</nav>';
    return $html;
}
Enter fullscreen mode Exit fullscreen mode

This auto-sizing approach is live on TrendVidStream, gracefully handling varying numbers of categories across different regions without arrows, dropdowns, or complex JavaScript.

The key insight: measure overflow, downsize progressively, and let CSS handle mobile with horizontal scroll.

Performance Considerations

The auto-sizing category bar was designed with Core Web Vitals in mind. The min-height: 48px on the container reserves space immediately, preventing Cumulative Layout Shift during the sizing calculation. The contain: layout property ensures that size changes within the category bar do not trigger layout recalculations on the rest of the page.

The JavaScript fit() function runs synchronously during DOMContentLoaded to avoid a flash of incorrectly sized chips. The resize observer uses a 150ms debounce to prevent excessive recalculations during window resizing.

On mobile, the horizontal scroll approach eliminates the need for JavaScript-based sizing entirely. Native scroll with momentum scrolling provides a smooth, familiar user experience. The scrollbar is hidden via CSS for a cleaner aesthetic, while the content remains fully accessible via touch scroll.

This component handles the real-world complexity of TrendVidStream, where the number of active categories varies by region. UAE might show 12 categories while Finland shows 8. The auto-sizing ensures consistent presentation regardless of how many categories are active.

The pattern is reusable: any chip-based or tag-based navigation that needs to handle a variable number of items can benefit from the progressive downsizing approach. Measure overflow, reduce size in steps, and use horizontal scroll as the mobile fallback.

Top comments (0)