HyTabPanel
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-hiddenbased on active state - Manages
tabindexfor keyboard navigation - Supports focus management for keyboard users
Examples
Basic usage
<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
<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
<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
<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
<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
<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
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>Slots
- default - The content to display when the tab panel is active
- toolbar - Panel-scoped controls rendered as a right-aligned header row above the content. Auto-promotes to
role="toolbar"with 2+ controls.
Methods
willUpdate()
Tracks if panel has ever been active (for lazy loading). Done in willUpdate — pre-render — so the flag lands in the SAME cycle the activation does: a lazy panel renders its content one frame earlier and no second update is scheduled (the old updated() placement tripped Lit's "scheduled an update after an update completed" dev warning on every activation). The isConnected guard preserves the SSR behavior (lazy panels don't render content server-side): unlike updated(), willUpdate DOES run during SSR — including