Skip to content

HyTabPanel

Usage examples on this page are written for Lit / plain HTML (<hy-tab-panel>). The same component ships as HyTabPanel in @whitespaceux/harmony-react (native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.

A tab panel component that displays content associated with a specific tab. Designed to work within a tab group system and supports lazy loading for performance optimization. Provides proper accessibility attributes and visibility management based on tab selection state.

Usage Guidelines

Tab panels should be used within an hy-tab-group component. Each panel is associated with a specific tab via matching name attributes. Only the active panel is visible at any time.

Lazy Loading

When the lazy attribute is set, panel content is only rendered after the panel has been activated at least once. This improves initial page load performance for tabs with heavy content.

Note: Lazy loading intentionally triggers a Lit "scheduled an update after an update completed" warning when a panel is first activated. This is expected behavior - the warning cannot be avoided without breaking SSR compatibility. The second render cycle is required to display content after the hasBeenActive state is set.

Accessibility

  • Uses role="tabpanel" for proper ARIA semantics
  • Sets aria-hidden based on active state
  • Manages tabindex for keyboard navigation
  • Supports focus management for keyboard users

Examples

Basic usage

html
<hy-tab-panel name="panel1">
  <h2>Panel Content</h2>
  <p>This content is displayed when the associated tab is selected.</p>
</hy-tab-panel>

Within tab group

html
<hy-tab-group>
  <hy-tab slot="tab" label="Overview" panel="overview-panel"></hy-tab>
  <hy-tab slot="tab" label="Details" panel="details-panel"></hy-tab>

  <hy-tab-panel slot="panel" name="overview-panel">
    <div class="overview-content">
      <h3>Project Overview</h3>
      <p>Summary of key project metrics and status.</p>
    </div>
  </hy-tab-panel>

  <hy-tab-panel slot="panel" name="details-panel">
    <div class="details-content">
      <h3>Detailed Information</h3>
      <p>Comprehensive project details and specifications.</p>
    </div>
  </hy-tab-panel>
</hy-tab-group>

Lazy loading for performance

html
<hy-tab-panel name="heavy-content" lazy>
  <div class="expensive-content">
    <h3>Resource-Intensive Content</h3>
    <div class="chart-container">
      <!-- Complex charts or heavy DOM content -->
      <canvas id="performance-chart"></canvas>
    </div>
    <div class="data-table">
      <!-- Large data tables -->
      <table>
        <!-- Thousands of rows -->
      </table>
    </div>
  </div>
</hy-tab-panel>

Vertical orientation

html
<hy-tab-group orientation="vertical-left">
  <hy-tab slot="tab" label="Dashboard" panel="dashboard"></hy-tab>
  <hy-tab slot="tab" label="Settings" panel="settings"></hy-tab>

  <hy-tab-panel slot="panel" name="dashboard" orientation="vertical-left">
    <div class="dashboard-layout">
      <div class="widget-grid">
        <div class="widget">
          <h4>Active Users</h4>
          <div class="metric">1,234</div>
        </div>
        <div class="widget">
          <h4>Revenue</h4>
          <div class="metric">$45,678</div>
        </div>
      </div>
    </div>
  </hy-tab-panel>

  <hy-tab-panel slot="panel" name="settings" orientation="vertical-left">
    <div class="settings-form">
      <h4>Application Settings</h4>
      <form>
        <label
          >Theme:
          <select>
            <option>Light</option>
            <option>Dark</option>
          </select></label
        >
        <label
          >Language:
          <select>
            <option>English</option>
            <option>Spanish</option>
          </select></label
        >
      </form>
    </div>
  </hy-tab-panel>
</hy-tab-group>

Form-based panels

html
<hy-tab-group>
  <hy-tab slot="tab" label="Personal Info" panel="personal"></hy-tab>
  <hy-tab slot="tab" label="Contact Details" panel="contact"></hy-tab>
  <hy-tab slot="tab" label="Preferences" panel="preferences"></hy-tab>

  <hy-tab-panel slot="panel" name="personal">
    <form class="personal-form">
      <h3>Personal Information</h3>
      <div class="form-row">
        <label>First Name: <input type="text" required /></label>
        <label>Last Name: <input type="text" required /></label>
      </div>
      <div class="form-row">
        <label>Date of Birth: <input type="date" /></label>
        <label
          >Gender:
          <select>
            <option>Prefer not to say</option>
          </select></label
        >
      </div>
    </form>
  </hy-tab-panel>

  <hy-tab-panel slot="panel" name="contact">
    <form class="contact-form">
      <h3>Contact Details</h3>
      <div class="form-row">
        <label>Email: <input type="email" required /></label>
        <label>Phone: <input type="tel" /></label>
      </div>
      <div class="form-row">
        <label>Address: <textarea rows="3"></textarea></label>
      </div>
    </form>
  </hy-tab-panel>

  <hy-tab-panel slot="panel" name="preferences" lazy>
    <form class="preferences-form">
      <h3>User Preferences</h3>
      <div class="preference-group">
        <h4>Notifications</h4>
        <label><input type="checkbox" /> Email notifications</label>
        <label><input type="checkbox" /> SMS notifications</label>
        <label><input type="checkbox" /> Push notifications</label>
      </div>
      <div class="preference-group">
        <h4>Privacy</h4>
        <label><input type="radio" name="profile" /> Public profile</label>
        <label><input type="radio" name="profile" /> Private profile</label>
      </div>
    </form>
  </hy-tab-panel>
</hy-tab-group>

Dynamic content with lazy loading

html
<hy-tab-panel name="analytics" lazy>
  <div class="analytics-dashboard">
    <h3>Analytics Dashboard</h3>
    <div id="analytics-content">
      <!-- Content loaded dynamically when panel first becomes active -->
    </div>
  </div>
  <script>
    // This code only runs when panel is first activated
    fetch('/api/analytics')
      .then((response) => response.json())
      .then((data) => {
        document.getElementById('analytics-content').innerHTML = renderAnalytics(data);
      });
  </script>
</hy-tab-panel>

Programmatic Access

javascript
const panel = document.querySelector('hy-tab-panel[name="settings"]');

// Check if panel is active
console.log(panel.active);

// Check if panel uses lazy loading
console.log(panel.lazy);

// Get panel name
console.log(panel.name);

Header toolbar

The toolbar slot hosts controls scoped to this panel (e.g. a Find + view-switch, or an Edit → Save/Cancel inline-edit set). It renders as the panel's first row, right-aligned, before the content in DOM order so it's announced first. The heading stays consumer-authored in the default slot — compose a Polaris-style header by placing the heading at the top of the content and letting the toolbar float to the trailing edge.

The toolbar auto-promotes to role="toolbar" when it holds 2 or more controls; name it with toolbar-label. The toolbar wrapper renders only when the slot is


### Inline-edit toolbar

```html
<hy-tab-panel slot="panel" name="general">
<hy-button slot="toolbar" emphasis="plain">Cancel</hy-button>
<hy-button slot="toolbar" variant="brand">Save</hy-button>
<h2>General</h2>

</hy-tab-panel>

API

Properties

PropertyAttributeTypeDefaultDescription
namenamestring''The unique identifier for this tab panel, used to match with tab's panel attribute.
activeactivebooleanfalseMarks the tab panel as active/visible. Typically managed by parent tab-group.
orientationorientation'horizontal' | 'vertical-left' | 'vertical-right''horizontal'The orientation of the tab panel (inherited from tab-group).
lazylazybooleanfalseWhether this panel is lazy-loaded (content only renders when first activated). Improves initial page load performance for tabs with heavy content.
toolbarLabeltoolbar-labelstring''Accessible name applied as aria-label to the toolbar when it auto-promotes to role="toolbar" (2+ controls). Recommended whenever the toolbar carries multiple controls so the toolbar has a name.
labellabelstring''Accessible name for the tabpanel, announced by screen readers. When omitted, the parent hy-tab-group syncs it from the matching tab's label. Forwarded as a string because a plain IDREF aria-labelledby cannot reach role="tab" in the sibling tab's shadow root.

Slots

SlotDescription
defaultThe content to display when the tab panel is active
toolbarPanel-scoped controls rendered as a right-aligned header row above the content. Auto-promotes to role="toolbar" with 2+ controls.

CSS Parts

PartDescription
baseThe tab panel's base wrapper element
toolbarThe panel header toolbar region

CSS Custom Properties

PropertyDescription
--hy-tab-panel-toolbar-gapGap between toolbar controls and below the toolbar. Default: var(--hy-gap-layout-sm).

Built with Lit. Documented with VitePress.