<label>Direction: <button id="direction">Row</button></label>
	<button id="add-tab">Add Tab</button>
<wc-tab-panel>
		<h1 slot="tab">Tab A</h1>
		<div slot="content">Bacon ipsum dolor amet buffalo chicken pastrami pork. Fatback flank picanha spare ribs chuck alcatra. Spare ribs pork
		loin sausage t-bone burgdoggen pastrami. Spare ribs kevin beef ribs alcatra ham hock turkey ribeye, prosciutto strip
		steak bacon venison shank tri-tip landjaeger beef.
		
		Tail pork loin kielbasa bresaola capicola, alcatra chislic cupim tenderloin pig brisket. Cow pork loin meatball, chislic
		salami hamburger beef ribs t-bone strip steak kevin biltong pastrami landjaeger. Cupim doner pastrami, andouille
		turducken sirloin landjaeger salami. Flank filet mignon short ribs, boudin sirloin ball tip landjaeger sausage ground
		round buffalo. Ham hock rump leberkas, doner capicola short loin bacon chicken pig tongue jerky ball tip turducken
		shankle cupim. Short ribs tongue bacon spare ribs doner. Frankfurter jowl turkey filet mignon beef kielbasa shank
		meatball beef ribs ball tip turducken tail fatback cow pig.</div>
		<h1 slot="tab">Tab B</h1>
		<div slot="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ultrices et velit eu rutrum. Etiam bibendum ipsum
		vitae vestibulum venenatis. Mauris volutpat libero quam. Vestibulum quis ipsum tempor, egestas libero tristique,
		malesuada nibh. Mauris vulputate sapien vitae nibh facilisis, ac pretium purus dictum. Mauris venenatis, turpis ac
		dictum sagittis, odio purus tincidunt elit, sit amet fermentum nibh lorem vel tortor. Mauris in libero ac diam volutpat
		tempor. In hendrerit sed sem sit amet pellentesque. Vestibulum ac massa vitae odio laoreet dictum at rhoncus elit.
		
		Integer ac est vel enim bibendum tempus. Aenean dignissim a augue laoreet elementum. Vivamus sit amet facilisis ipsum,
		id fringilla tortor. Integer vitae est ultricies, tincidunt purus id, eleifend enim. Integer egestas, augue vel
		malesuada aliquet, elit turpis condimentum odio, a tristique ipsum nisi scelerisque erat. Vivamus tortor lacus,
		tristique ut enim nec, consequat porttitor odio. Phasellus facilisis enim ut nisl molestie, sed convallis ipsum
		vulputate. Donec mollis vitae nibh vitae tincidunt. Vestibulum lobortis eu eros vitae luctus. Vestibulum a augue non
		lacus dignissim dignissim. Sed eget ipsum lacus. Donec a augue faucibus, vulputate nibh eu, posuere arcu. Curabitur
		risus nunc, hendrerit vel mi nec, vehicula gravida nisi. Nam a dolor eu velit dignissim facilisis. Morbi tincidunt
		ligula in nibh dignissim, sed ornare diam iaculis.</div>
		<h1 slot="tab">Tab C</h1>
		<div slot="content">Veggies es bonus vobis, proinde vos postulo essum magis kohlrabi welsh onion daikon amaranth tatsoi tomatillo melon
		azuki bean garlic.
		
		Gumbo beet greens corn soko endive gumbo gourd. Parsley shallot courgette tatsoi pea sprouts fava bean collard greens
		dandelion okra wakame tomato. Dandelion cucumber earthnut pea peanut soko zucchini.
		
		Turnip greens yarrow ricebean rutabaga endive cauliflower sea lettuce kohlrabi amaranth water spinach avocado daikon
		napa cabbage asparagus winter purslane kale. Celery potato scallion desert raisin horseradish spinach carrot soko. Lotus
		root water spinach fennel kombu maize bamboo shoot green bean swiss chard seakale pumpkin onion chickpea gram corn pea.
		Brussels sprout coriander water chestnut gourd swiss chard wakame kohlrabi beetroot carrot watercress. Corn amaranth
		salsify bunya nuts nori azuki bean chickweed potato bell pepper artichoke.  </div>
</wc-tab-panel>
label { display: block; margin-bottom: 1rem; }
h1 { margin: 0; }

wc-tab-panel {
		--tab-gap: 1rem;
}
class WcTabPanel extends HTMLElement {
    static observedAttributes = ["selected-index", "direction"];
    #selectedIndex = 0;
    #direction = "row";
    constructor() {
        super();
        this.bind(this);
    }
    bind(element) {
        element.render = element.render.bind(element);
        element.attachEvents = element.attachEvents.bind(element);
        element.cacheDom = element.cacheDom.bind(element);
        element.onTabClick = element.onTabClick.bind(element);
        element.selectTabByIndex = element.selectTabByIndex.bind(element);
        element.onContentSlotChange = element.onContentSlotChange.bind(element);
        element.onTabSlotChange = element.onTabSlotChange.bind(element);
    }
    connectedCallback() {
        this.render();
        this.cacheDom();
        this.attachEvents();
        this.dom.tabs[this.#selectedIndex]?.classList.add("selected");
        this.dom.contents[this.#selectedIndex]?.classList.add("selected");
    }
    render() {
        this.shadow = this.attachShadow({ mode: "open" });
        this.shadow.innerHTML = `
                <style>
                    :host { display: flex; flex-direction: column; }
                    :host([direction="column"]) { flex-direction: row; }
                    :host([direction="column"]) .tabs { flex-direction: column; }
                    .tabs { display: flex; flex-direction: row; flex-wrap: nowrap; gap: var(--tab-gap, 0px); }
                    
                    .tabs ::slotted(*) { padding: 5px; border: 1px solid #ccc; user-select: none; cursor: pointer; }
                    .tabs ::slotted(.selected) { background: #efefef; }
                    .tab-contents ::slotted(*) { display: none; }
                    .tab-contents ::slotted(.selected) { display: block; padding: 5px; }
                </style>
                <div class="tabs">
                    <slot id="tab-slot" name="tab"></slot>
                </div>
                <div class="tab-contents">
                    <slot id="content-slot" name="content"></slot>
                </div>
            `;
    }
    cacheDom() {
        this.dom = {
            tabSlot: this.shadow.querySelector("#tab-slot"),
            contentSlot: this.shadow.querySelector("#content-slot")
        };
        this.dom.tabs = this.dom.tabSlot.assignedElements();
        this.dom.contents = this.dom.contentSlot.assignedElements();
    }
    attachEvents() {
        this.dom.tabSlot.addEventListener("click", this.onTabClick);
        this.dom.tabSlot.addEventListener("slotchange", this.onTabSlotChange);
        this.dom.contentSlot.addEventListener("slotchange", this.onContentSlotChange);
    }
    onTabSlotChange(){
        this.dom.tabs = this.dom.tabSlot.assignedElements();
    }
    onContentSlotChange(){
        this.dom.contents = this.dom.contentSlot.assignedElements();
    }
    onTabClick(e) {
        const target = e.target;
        if (target.slot === "tab") {
            const tabIndex = this.dom.tabs.indexOf(target);
            this.selectTabByIndex(tabIndex);
        }
    }
    selectTabByIndex(index) {
        const tab = this.dom.tabs[index];
        const content = this.dom.contents[index];
        if (!tab || !content) return;
        this.dom.contents.forEach(p => p.classList.remove("selected"));
        this.dom.tabs.forEach(p => p.classList.remove("selected"));
        content.classList.add("selected");
        tab.classList.add("selected");
    }
    attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue !== newValue) {
            if(name === "selected-index"){
                this.selectedIndex = newValue;
            } else {
                this[name] = newValue;
            }
        }
    }
    set selectedIndex(value) {
        this.#selectedIndex = value;
    }
    get selectedIndex() {
        return this.#selectedIndex;
    }
    set direction(value){
        this.#direction = value;
        this.setAttribute("direction", value);
    }
    get direction(){
        return this.#direction;
    }
}

customElements.define("wc-tab-panel", WcTabPanel);

//Page UI

document.addEventListener("DOMContentLoaded", () => {
			const panel = document.querySelector("wc-tab-panel");
			const dirButton = document.querySelector("#direction");
			const addButton = document.querySelector("#add-tab");
			let charCode = 68;

			dirButton.addEventListener("click", () => {
				if (panel.direction === "row") {
					panel.direction = "column";
					dirButton.textContent = "column";
				} else if (panel.direction === "column") {
					panel.direction = "row";
					dirButton.textContent = "row";
				}
			});
			addButton.addEventListener("click", () => {
				const letter = String.fromCharCode(charCode);
				const h1 = document.createElement("h1");
				h1.textContent = `Tab ${letter}`;
				h1.slot = "tab";
				const content = document.createElement("div");
				content.textContent = `Hello Tab ${letter}`;
				content.slot = "content";
				panel.appendChild(h1);
				panel.appendChild(content);
				charCode++;
			});
		});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.