AlpineJS Tabs with URL Fragments

Most Alpine tab implementations use internal x-data values to control tab state. I needed to have the tab selection as part of the URL, specifically, the URL hash or fragment. Therefore, someone can link directly to a tab and tab state is maintained on refresh or navigation. Handling this with Alpine turned out to be very simple.

First we need to write a hashTracker function that listens for changes in the URL fragment.

<script>
    function hashTracker(values = []) {
        return {
            allowedValues: values,
            currentHash: '',

            init() {
                this.updateHash();
                window.addEventListener('hashchange', () => this.updateHash());
            },

            updateHash() {
                const hash = location.hash.substring(1);
                this.currentHash = this.allowedValues.includes(hash) ? hash : this.allowedValues[0];
            }
        };
    }
</script>

This function takes a list of hashes/fragments to observe, starts a listener that is called when the URL changes, and initializes the currentHash based on the current URL or defaults to the first observed value (the first tab).

Now, the HTML is very simple:

<div x-data="hashTracker(['tab1', 'tab2'])">
    <nav aria-label="Tabs">
        <a href="#tab1">Tab 1</a>
        <a href="#tab2">Tab 2</a>
    </nav>

    <div x-show="currentHash === 'tab1'">Tab 1 content</div>
    <div x-show="currentHash === 'tab2'">Tab 2 content</div>
</div>

First, we set the x-data on the div wrapper to the value of the bound hashTracker function. This ensures that the data changes based upon the current value of the URL hash.

Next we include our tabs. We do not need a click handler to change the value, we only need to update the URL which will trigger the data change.

Finally, the tab content is hidden or shown based on the currentHash value that is defined in the hashTracker function.

A downside of this approach is that you are hijacking the URL fragment for your tabs. This means that you cannot have multiple tab groups per page and you cannot use the fragment to target elements on the page. However, if your needs don't require that, this may be a nice way to have linkable tabs in your UI.