Tabpanel

Tabpanel

Overview

A tab panel pattern consists of tabs and tab panels. The tabs are visible at all times, but only one tab panel is visible at a time. The tabs act like buttons. When the tab is activated, the corresponding panel is made visible, and all other panels are hidden. Usually the active tab is styled differently from the inactive tabs, to set it apart visually.



Turn on a screen reader to experience this example in action.

Attribute/Option Description
panelColor This attribute/option specifies the background color for the selected panel where the content is displayed. The selected tab button is also displayed with the same background color. If this option is not specified, a default white color is used.
orientation This attribute/option specifies if the tab list to be displayed horizontally(HORIZONTAL) or vertically(VERTICAL) . If this option is not specified, the tablist is displayed horizontally.


Making content perceivable means making the output available to the user's senses, namely sight, sound, and touch (in the case of people who use Braille output devices). We won't worry about tasting or smelling web pages!
Making content operable means making the input mechanisms robust enough to accept a wide range of devices and methods, including keyboard, mouse, touch, gestures, single-switch devices, and so on.
Making content understandable means making the message and the interface easy to use and comprehend.
Making content robust means ensuring it works across a wide range of devices, with both forward and backward compatibility.

HTML Source Code

<table class="data">
    <tr>
        <th width="150px" >Attribute/Option</th>
        <th>Description</th>
    </tr>
    <tr>
        <td>panelColor</td>
        <td>This attribute/option specifies the background color for the selected panel where the content is displayed.
            The selected tab button is also displayed with the same background color. 
            If this option is not specified, a default white color is used.
        </td>
        </tr>
    <tr>
        <td>orientation</td>
        <td>This attribute/option specifies if the tab list to be displayed horizontally(HORIZONTAL) or vertically(VERTICAL) .
            If this option is not specified, the tablist is displayed horizontally.
        </td>
    </tr>  
</table>
<br/><br/>
<div id="tab-panel1">
    <div role="tablist">
        <button role="tab" aria-controls="panel-perceivable">Perceivable</button>
        <button role="tab" aria-controls="panel-operable" aria-selected="true">Operable</button>
        <button role="tab" aria-controls="panel-understandable">Understandable</button>
        <button role="tab" aria-controls="panel-robust">Robust</button>
    </div>
    
    <div class="panel-container" >
        <div role="tabpanel" id="panel-perceivable" class="deque-tabpanel-tabpanel">
          Making content <em>perceivable</em> means making the <strong>output</strong>
          available to the user's senses, namely sight, sound, and touch (in the case
          of people who use Braille output devices). We won't worry about tasting or
          smelling web pages!
        </div>
        <div role="tabpanel" id="panel-operable" class="deque-tabpanel-tabpanel deque-hidden">
          Making content <em>operable</em> means making the <strong>input mechanisms</strong>
          robust enough to accept a wide range of devices and methods, including keyboard,
          mouse, touch, gestures, single-switch devices, and so on.
        </div>
        <div role="tabpanel" id="panel-understandable" class="deque-tabpanel-tabpanel deque-hidden">
          Making content <em>understandable</em> means making the message and the interface
          easy to use and comprehend.
        </div>
        <div role="tabpanel" id="panel-robust" class="deque-tabpanel-tabpanel deque-hidden">
          Making content <em>robust</em> means ensuring it works across a wide range of
          devices, with both forward and backward compatibility.
        </div>
      </div>

</div>

JavaScript Source Code

var langText = {
    "errorNodeNotObject": "Node provided is not a DOM object.",
    "errorNoTablist": "element with tablist role not defined",
    "errorAriaControls":"'aria-controls' not specified or incorrect for a tab.",
    "errorAriaControlsNotId":"'aria-controls' specified non existant element id",
    "errorPanelContainer":"element with panel-container class not defined",
    "errorNoTabButton":"could not find active tab button",

}

var tabPanel;
window.addEventListener('load', function () {
  options = {};
  options.panelColor = "#f8fcda";
  //options.orientation = VERTICAL;
  tabPanel = new Tabpanel(document.getElementById("tab-panel1"), options);

});

const HORIZONTAL = 0;
const VERTICAL = 1;

class Tabpanel {
    constructor(domNode, options) {

        this.orientation = HORIZONTAL;
        this.panelColor = "white";
        
        if ( typeof options != "object" ) options = {};
        if( (typeof options["orientation"] === "number") && ( (options["orientation"] === HORIZONTAL) || (options["orientation"] === VERTICAL)) )
            this.orientation = options["orientation"];
        if(typeof options["panelColor"] === "string")
            this.panelColor = options["panelColor"];

        if( (typeof domNode != "object" ) && (typeof domNode.nodeName === "undefined") )
        {
            console.log(langText.errorNodeNotObject);
            return;
        }
        this.domNode = domNode;
        
        this.tablist = this.domNode.querySelector('[role="tablist"]');
        if(!this.tablist)
        {
            console.log(langText.errorNoTablist);
            return;
        }
        this.tablist.classList.add("tablist");
    
        
        this.tabButtons = this.tablist.querySelectorAll('[role="tab"]');
        for(var i=0; i < this.tabButtons.length; i++) 
        {
            var btn = this.tabButtons[i];
            if(btn)
            {
                btn.classList.add("tab-button");
                if (this.tabButtons[i].getAttribute("aria-selected") == "true" )
                    this.selectTab(this.tabButtons[i]);
                if ( typeof this.tabButtons[i].getAttribute("aria-controls") !== "string" )
                {
                    console.log(langText.errorAriaControls);
                    return;
                }
                var panelId = this.tabButtons[i].getAttribute("aria-controls");
                var panel = document.getElementById( panelId);
                if(!panel)
                {
                    console.log( langText.errorAriaControlsNotId + " " + panelId);
                    return;
                }
                if(this.orientation == VERTICAL)
                    btn.classList.add("tab-button-vertical");
                else
                    btn.classList.add("tab-button-horizontal");

                btn.addEventListener("click", this.onClick.bind(this));
                btn.addEventListener('keydown', this.onKeyDown.bind(this));
            }
        }
        this.tabpanelContainer = this.domNode.querySelector('.panel-container');
        if(!this.tabpanelContainer)
        {
            console.log(langText.errorPanelContainer);
            return;
        }
        this.tabpanelContainer.style.backgroundColor = this.panelColor;

        if(this.orientation == VERTICAL)
        {
            this.domNode.style.display = "flex";
            this.tablist.classList.add("tablist-vertical");
            this.tabpanelContainer.classList.add("tabpanel-vertical");
        }
        else
        {
            this.domNode.style.display = "block";
            this.tabpanelContainer.classList.add("tabpanel-horizontal");
        }
        
    }

    selectTab(selectTab)
    {
        for(var i=0; i < this.tabButtons.length; i++)
        {
            this.tabButtons[i].setAttribute("aria-selected","false");   
            var panelId = this.tabButtons[i].getAttribute("aria-controls");
            var panel = document.getElementById( panelId);      
            var btn = this.tabButtons[i];
            btn.setAttribute("tabindex", "-1");
            if(this.tabButtons[i] == selectTab)
            {
                this.tabButtons[i].setAttribute("aria-selected","true"); 
                btn.setAttribute("tabindex", "0");
                if( this.orientation == VERTICAL)
                    btn.style = "border-right: transparent; background-color: "+this.panelColor+";";
                else
                    btn.style = "border-bottom: inherit; background-color: "+this.panelColor+";";
                
                panel.classList.remove("hidden");
            }
            else
            {
                btn.style = "";
                panel.classList.add("hidden");
            }
                
        }    
    }

    getSelectedTabButton()
    {
        var selectedIndex = -1;
            for(var i=0; i < this.tabButtons.length; i++)
                if( this.tabButtons[i] == event.target)
                    selectedIndex = i;    
            if(selectedIndex == -1)
                console.log(langText.errorNoTabButton);  
                      
        return selectedIndex;    
    }

    showPreviousTab()
    {
        var selectedIndex = this.getSelectedTabButton();
        if(selectedIndex == 0 )
        {
            this.selectTab( this.tabButtons[this.tabButtons.length-1]);
            this.tabButtons[this.tabButtons.length-1].focus();
        }
        else
        {
            this.selectTab( this.tabButtons[selectedIndex-1]);
            this.tabButtons[selectedIndex-1].focus();
        }
    }

    showNextTab()
    {
        var selectedIndex = this.getSelectedTabButton();
        if(selectedIndex == this.tabButtons.length-1 )
        {
            this.tabButtons[0].focus();
            this.selectTab( this.tabButtons[0]);
        }
        else
        {
            this.tabButtons[selectedIndex+1].focus();
            this.selectTab( this.tabButtons[selectedIndex+1]);
            
        }
    }


    /* EVENT HANDLERS */
    onClick(event) {
        for(var i=0; i < this.tabButtons.length; i++)
        {
            this.tabButtons[i].setAttribute("aria-selected","false");   
            var panelId = this.tabButtons[i].getAttribute("aria-controls");
            var panel = document.getElementById( panelId);      
            if(this.tabButtons[i] == event.target)
            {
                this.selectTab(this.tabButtons[i]);
                return;
            }    
        }  
    }

    // Make sure to prevent page scrolling on space down
    
    onKeyDown(event) {
           
        switch (event.key) {
        
        case 'ArrowUp':
            this.showPreviousTab();
            //flag = true;
            break;
    
        case 'ArrowDown':
            this.showNextTab();
            //flag = true;
            break;
    
        case 'ArrowLeft':
            this.showPreviousTab();
            //flag = true;
            break;
    
        case 'ArrowRight':
            this.showNextTab();
            //flag = true;
            break;

        default:
            break;
        }
        
    }


}

CSS Source Code

.tablist {
    display: flex;
    align-items: stretch;
    width: fit-content;
    z-index: 10;
}
.tablist-vertical {
    flex-direction: column;
}

.tab-button {
    width: 100%;
    padding: 5px;
    background-color: #d7e0e5;
    border: 1px solid #a4a4a4;
    font-weight: 700;
    outline: none;
    border-top: 3px solid inherit;
    border-right: 3px solid inherit;
   
}
.tab-button-horizontal {  margin-left: 5px; }
.tab-button-vertical {  margin-top: 5px; }

.tab-button:focus {
    outline: 2px solid blue;
}
.tab-button[aria-selected = "true"] {
    border: 1px solid black;
}

.panel-container {
    padding: 10px;
    border: 1px solid black;
    
}

.tabpanel-vertical { margin-left: -1px;}
.tabpanel-horizontal { margin-top: -1px;}

.panel-vertical {
    border-left: none;
}

.panel-horizontal {
    border-top: none;
}

.hidden {
    display: none;
}

Copy and Paste Full Page Example