Plugin Directory

Changeset 3495763


Ignore:
Timestamp:
03/31/2026 03:32:12 PM (42 hours ago)
Author:
butterflymedia
Message:

Add Tab/Tabgroup ARIA accessibility, keyboard navigation, tabindex, and focus styles.

Location:
metro-tabs
Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • metro-tabs/trunk/css/metrotabs-block.css

    r3481381 r3495763  
    88
    99/* Tab bar – discrete bar with light border */
    10 .simple-tabs-block ul {
     10.metro-tabs-block ul {
    1111    margin: 2em 0;
    1212    gap: 0;
     
    1717}
    1818
    19 .simple-tabs-block ul li {
     19.metro-tabs-block ul li {
    2020    list-style: none;
    2121    margin: 0;
     
    2424
    2525/* Tab triggers – subtle padding, no heavy styling */
    26 .simple-tabs-block ul li .metrotabs-tab-trigger,
    27 .simple-tabs-block ul li a,
    28 .simple-tabs-block ul li a:link {
     26.metro-tabs-block ul li .metrotabs-tab-trigger,
     27.metro-tabs-block ul li a,
     28.metro-tabs-block ul li a:link {
    2929    display: inline-flex;
    3030    font-size: 1rem;
     
    4444}
    4545
    46 .simple-tabs-block ul li .metrotabs-tab-trigger:hover,
    47 .simple-tabs-block ul li a:hover {
     46.metro-tabs-block ul li .metrotabs-tab-trigger:hover,
     47.metro-tabs-block ul li a:hover {
    4848    color: #1d2327;
    4949}
    5050
    5151/* Active tab – discrete underline */
    52 .simple-tabs-block ul li .metrotabs-tab-trigger.active,
    53 .simple-tabs-block ul li a.active {
     52.metro-tabs-block ul li .metrotabs-tab-trigger.active,
     53.metro-tabs-block ul li a.active {
    5454    color: #1d2327;
    5555    border-bottom-color: currentColor;
    5656}
    5757
    58 .simple-tabs-block ul li .metrotabs-tab-trigger,
    59 .simple-tabs-block ul li a,
    60 .simple-tabs-block.simple-tabs-block-collapsible
    61     .simple-tabs-block-nav
    62     li.simple-tabs-block-selected
     58/* Focus indicator for keyboard navigation */
     59.metro-tabs-block ul li .metrotabs-tab-trigger:focus-visible {
     60    outline: 2px solid #2271b1;
     61    outline-offset: -2px;
     62    border-radius: 2px;
     63}
     64
     65/* Tab panel focus indicator */
     66.metro-tabs-block .tab-pane:focus-visible {
     67    outline: 2px solid #2271b1;
     68    outline-offset: 2px;
     69    border-radius: 2px;
     70}
     71
     72.metro-tabs-block ul li .metrotabs-tab-trigger,
     73.metro-tabs-block ul li a,
     74.metro-tabs-block.metro-tabs-block-collapsible
     75    .metro-tabs-block-nav
     76    li.metro-tabs-block-selected
    6377    a {
    6478    cursor: pointer;
    6579}
    6680
    67 .simple-tabs-block .ui-tabs-hide {
     81.metro-tabs-block .ui-tabs-hide {
    6882    display: none !important;
    6983}
    7084
    7185/* Tab panes */
    72 .simple-tabs-block .tab-pane {
     86.metro-tabs-block .tab-pane {
    7387    padding: 1em 0 0;
    7488}
    7589
    76 .simple-tabs-block .ui-tabs-panel {
     90.metro-tabs-block .ui-tabs-panel {
    7791    overflow: hidden;
    7892    background: #fff;
  • metro-tabs/trunk/js/metrotabs-block.js

    r3481381 r3495763  
    3434    }
    3535
    36     var simpleTabsBlock = document.createElement('div');
    37     simpleTabsBlock.className = 'simple-tabs-block';
     36    var metroTabsBlock = document.createElement('div');
     37    metroTabsBlock.className = 'metro-tabs-block';
    3838
    3939    var tabUl = document.createElement('ul');
     40    tabUl.setAttribute('role', 'tablist');
     41    tabUl.setAttribute('aria-orientation', 'horizontal');
     42
    4043    var wrappersToRemove = [];
    4144    var slugs = [];
     45    var tabIds = [];
    4246
    4347    for (var i = 0; i < tabItems.length; i++) {
    4448        var tabItem = tabItems[i];
    4549        var slug = idPrefix + slugify(tabItem.textContent);
     50        var tabId = slug + '-tab';
    4651        slugs.push(slug);
     52        tabIds.push(tabId);
    4753
    4854        var li = document.createElement('li');
     55        li.setAttribute('role', 'presentation');
     56
    4957        var btn = document.createElement('button');
    5058        btn.type = 'button';
    5159        btn.className = 'metrotabs-tab-trigger';
    52         btn.setAttribute('data-pane-id', slug);
     60        btn.setAttribute('role', 'tab');
     61        btn.setAttribute('id', tabId);
     62        btn.setAttribute('aria-selected', 'false');
     63        btn.setAttribute('aria-controls', slug);
     64        btn.setAttribute('tabindex', '-1');
    5365        btn.textContent = tabItem.textContent;
    5466        li.appendChild(btn);
     
    5971    }
    6072
    61     simpleTabsBlock.appendChild(tabUl);
     73    metroTabsBlock.appendChild(tabUl);
    6274
    6375    for (var j = 0; j < tabContents.length; j++) {
     
    6678        div.id = slugs[j];
    6779        div.className = 'tab-pane';
     80        div.setAttribute('role', 'tabpanel');
     81        div.setAttribute('aria-labelledby', tabIds[j]);
     82        div.setAttribute('tabindex', '0');
    6883        div.innerHTML = tabContent.innerHTML;
    69         simpleTabsBlock.appendChild(div);
     84        metroTabsBlock.appendChild(div);
    7085        tabContent.parentNode.removeChild(tabContent);
    7186    }
    7287
    73     container.insertBefore(simpleTabsBlock, container.firstChild);
     88    container.insertBefore(metroTabsBlock, container.firstChild);
    7489
    7590    wrappersToRemove.forEach(function (w) {
     
    7994    });
    8095
    81     initSimpleTabs(simpleTabsBlock);
     96    initMetroTabs(metroTabsBlock);
    8297}
    8398
     
    89104}
    90105
    91 function initSimpleTabs(container) {
    92     var tabs = container.querySelectorAll('ul > li > .metrotabs-tab-trigger');
    93     var tabPanes = container.querySelectorAll('.tab-pane');
     106function initMetroTabs(container) {
     107    var tabs = Array.prototype.slice.call(
     108        container.querySelectorAll('[role="tab"]')
     109    );
     110    var tabPanes = container.querySelectorAll('[role="tabpanel"]');
    94111
    95     function activateTab(tab) {
     112    function activateTab(tab, setFocus) {
    96113        if (!tab) return;
    97114
    98         tabs.forEach(function (t) { t.classList.remove('active'); });
     115        // Deactivate all tabs
     116        tabs.forEach(function (t) {
     117            t.classList.remove('active');
     118            t.setAttribute('aria-selected', 'false');
     119            t.setAttribute('tabindex', '-1');
     120        });
     121
     122        // Hide all panels
    99123        tabPanes.forEach(function (p) { p.style.display = 'none'; });
    100124
     125        // Activate the selected tab
    101126        tab.classList.add('active');
    102         var activePaneId = tab.getAttribute('data-pane-id');
    103         var activePane = activePaneId ? container.querySelector('#' + activePaneId) : null;
     127        tab.setAttribute('aria-selected', 'true');
     128        tab.setAttribute('tabindex', '0');
     129
     130        var activePaneId = tab.getAttribute('aria-controls');
     131        var activePane = activePaneId ? container.querySelector('#' + CSS.escape(activePaneId)) : null;
    104132        if (activePane) {
    105133            activePane.style.display = 'block';
    106134        }
     135
     136        if (setFocus) {
     137            tab.focus();
     138        }
    107139    }
    108140
     141    // Click handler
    109142    tabs.forEach(function (tab) {
    110143        tab.addEventListener('click', function (event) {
    111144            event.preventDefault();
    112             activateTab(tab);
     145            activateTab(tab, true);
     146        });
     147    });
     148
     149    // Keyboard navigation (WAI-ARIA Tabs pattern)
     150    tabs.forEach(function (tab) {
     151        tab.addEventListener('keydown', function (event) {
     152            var key = event.key;
     153            var index = tabs.indexOf(tab);
     154            var newTab = null;
     155
     156            if (key === 'ArrowRight') {
     157                newTab = tabs[(index + 1) % tabs.length];
     158            } else if (key === 'ArrowLeft') {
     159                newTab = tabs[(index - 1 + tabs.length) % tabs.length];
     160            } else if (key === 'Home') {
     161                newTab = tabs[0];
     162            } else if (key === 'End') {
     163                newTab = tabs[tabs.length - 1];
     164            }
     165
     166            if (newTab) {
     167                event.preventDefault();
     168                activateTab(newTab, true);
     169            }
    113170        });
    114171    });
     
    118175        if (hash) {
    119176            var paneId = hash.substring(1);
    120             var activeTab = container.querySelector('ul > li > .metrotabs-tab-trigger[data-pane-id="' + paneId + '"]');
    121             activateTab(activeTab);
     177            var activeTab = container.querySelector('[role="tab"][aria-controls="' + CSS.escape(paneId) + '"]');
     178            activateTab(activeTab, false);
    122179        } else if (tabs.length > 0) {
    123             activateTab(tabs[0]);
     180            activateTab(tabs[0], false);
    124181        }
    125182    }
  • metro-tabs/trunk/metro-tabs.php

    r3481381 r3495763  
    44 * Plugin URI: https://getbutterfly.com/wordpress-plugins/metro-tabs/
    55 * Description: Add responsive tab blocks to posts and pages. Group multiple tabs, add any blocks inside each tab. No jQuery, no dependencies.
    6  * Version: 1.1.4
     6 * Version: 1.1.5
    77 * Author: Ciprian Popescu
    88 * Author URI: https://getbutterfly.com/
     
    2323}
    2424
    25 define( 'METRO_TABS_VERSION', '1.1.4' );
     25define( 'METRO_TABS_VERSION', '1.1.5' );
    2626define( 'METRO_TABS_PLUGIN_FILE', __FILE__ );
    2727define( 'METRO_TABS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
  • metro-tabs/trunk/readme.txt

    r3495739 r3495763  
    66Requires at least: 6.0
    77Tested up to: 7.0
    8 Stable tag: 1.1.4
     8Stable tag: 1.1.5
    99License: GNU General Public License v3 or later
    1010License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    47474. Back-end block editor with Tab Group and Tab Item blocks
    48485. Back-end block editor with Tab Group and Tab Item blocks
     496. Source code demonstrating ARIA roles and keyboard navigation attributes
    4950
    5051== Changelog ==
     52
     53= 1.1.5 =
     54* Add WAI-ARIA Tabs pattern (roles, states, and properties)
     55* Add keyboard navigation (Arrow keys, Home, End) with automatic activation
     56* Add roving tabindex and visible focus styles for keyboard users
     57* Rename .simple-tabs-block CSS class to .metro-tabs-block
    5158
    5259= 1.1.4 =
Note: See TracChangeset for help on using the changeset viewer.