Tabs display layered content sections where only one panel is visible at a time. Users switch between panels by clicking tab buttons or using arrow keys to navigate the tab list.
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs>
<div ngTabList selectionMode="follow" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 0.75rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab][aria-selected='false']:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
border-bottom-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="material-tabs">
<div ngTabList selectionMode="follow" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
<div class="bottom-border"></div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab]:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--bright-blue);
}
.bottom-border {
position: absolute;
pointer-events: none;
left: 0;
bottom: 0;
height: 3px;
width: calc(100% / 3);
background-color: var(--bright-blue);
transition: all 0.2s ease-in-out;
transform: translateX(0%);
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
transform: translateX(200%);
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="retro-tabs">
<div ngTabList selectionMode="follow" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
</div>
<div ngTabPanel value="movie">
<ng-template ngTabContent>Line 1</ng-template>
</div>
<div ngTabPanel value="theatres">
<ng-template ngTabContent>Line 2</ng-template>
</div>
<div ngTabPanel value="showtimes">
<ng-template ngTabContent>Line 3</ng-template>
</div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
font-family: 'Press Start 2P';
--retro-button-color: var(--bright-blue);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngTabs] {
width: 600px;
}
[ngTabList] {
gap: 1rem;
display: flex;
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem;
cursor: pointer;
text-align: center;
color: #000;
background-color: #fff;
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngTab]:focus,
[ngTab]:hover {
transform: translate(1px, 1px);
}
[ngTab]:focus {
outline-offset: 2px;
outline: 4px dashed var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}
[ngTab]:active,
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngTabPanel] {
flex: 1;
font-size: 1.5rem;
color: #000;
background-color: #fff;
padding: 1rem;
margin-top: 1rem;
min-height: 100px;
display: grid;
place-items: center;
box-shadow: var(--retro-flat-shadow);
background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
background-size: 100% 4px;
}
[ngTabPanel]:focus {
outline-offset: 4px;
outline: 4px dashed var(--bright-blue);
}
[ngTabPanel][inert] {
display: none;
}
Tabs work well for organizing related content into distinct sections where users switch between different views or categories.
Use tabs when:
Avoid tabs when:
When selection follows focus, tabs activate immediately as you navigate with arrow keys. This provides instant feedback and works well for lightweight content.
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs>
<div ngTabList selectionMode="follow" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 0.75rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab][aria-selected='false']:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
border-bottom-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="material-tabs">
<div ngTabList selectionMode="follow" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
<div class="bottom-border"></div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab]:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--bright-blue);
}
.bottom-border {
position: absolute;
pointer-events: none;
left: 0;
bottom: 0;
height: 3px;
width: calc(100% / 3);
background-color: var(--bright-blue);
transition: all 0.2s ease-in-out;
transform: translateX(0%);
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
transform: translateX(200%);
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="retro-tabs">
<div ngTabList selectionMode="follow" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
</div>
<div ngTabPanel value="movie">
<ng-template ngTabContent>Line 1</ng-template>
</div>
<div ngTabPanel value="theatres">
<ng-template ngTabContent>Line 2</ng-template>
</div>
<div ngTabPanel value="showtimes">
<ng-template ngTabContent>Line 3</ng-template>
</div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
font-family: 'Press Start 2P';
--retro-button-color: var(--bright-blue);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngTabs] {
width: 600px;
}
[ngTabList] {
gap: 1rem;
display: flex;
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem;
cursor: pointer;
text-align: center;
color: #000;
background-color: #fff;
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngTab]:focus,
[ngTab]:hover {
transform: translate(1px, 1px);
}
[ngTab]:focus {
outline-offset: 2px;
outline: 4px dashed var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}
[ngTab]:active,
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngTabPanel] {
flex: 1;
font-size: 1.5rem;
color: #000;
background-color: #fff;
padding: 1rem;
margin-top: 1rem;
min-height: 100px;
display: grid;
place-items: center;
box-shadow: var(--retro-flat-shadow);
background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
background-size: 100% 4px;
}
[ngTabPanel]:focus {
outline-offset: 4px;
outline: 4px dashed var(--bright-blue);
}
[ngTabPanel][inert] {
display: none;
}
Set [selectionMode]="'follow'" on the tab list to enable this behavior.
With manual activation, arrow keys move focus between tabs without changing the selected tab. Users press Space or Enter to activate the focused tab.
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs>
<div ngTabList selectionMode="explicit" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 0.75rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab][aria-selected='false']:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
border-bottom-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="material-tabs">
<div ngTabList selectionMode="explicit" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
<div class="bottom-border"></div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab]:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--bright-blue);
}
.bottom-border {
position: absolute;
pointer-events: none;
left: 0;
bottom: 0;
height: 3px;
width: calc(100% / 3);
background-color: var(--bright-blue);
transition: all 0.2s ease-in-out;
transform: translateX(0%);
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
transform: translateX(200%);
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="retro-tabs">
<div ngTabList selectionMode="explicit" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes">Reviews</div>
</div>
<div ngTabPanel value="movie">
<ng-template ngTabContent>Line 1</ng-template>
</div>
<div ngTabPanel value="theatres">
<ng-template ngTabContent>Line 2</ng-template>
</div>
<div ngTabPanel value="showtimes">
<ng-template ngTabContent>Line 3</ng-template>
</div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
font-family: 'Press Start 2P';
--retro-button-color: var(--bright-blue);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast),
0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
0px -4px 0px 0px var(--quaternary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--quaternary-contrast), 0px 4px 0px 0px var(--quaternary-contrast),
-4px 0px 0px 0px var(--quaternary-contrast), 0px -4px 0px 0px var(--quaternary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast),
0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
0px -4px 0px 0px var(--quaternary-contrast), 8px 8px 0px 0px var(--quaternary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--quaternary-contrast),
0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
0px -4px 0px 0px var(--quaternary-contrast), 0px 0px 0px 0px var(--quaternary-contrast);
}
[ngTabs] {
width: 600px;
}
[ngTabList] {
gap: 1rem;
display: flex;
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem;
cursor: pointer;
text-align: center;
color: #000;
background-color: #fff;
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngTab]:focus,
[ngTab]:hover {
transform: translate(1px, 1px);
}
[ngTab]:focus {
outline-offset: 2px;
outline: 4px dashed var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}
[ngTab]:active,
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngTabPanel] {
flex: 1;
font-size: 1.5rem;
color: #000;
background-color: #fff;
padding: 1rem;
margin-top: 1rem;
min-height: 100px;
display: grid;
place-items: center;
box-shadow: var(--retro-flat-shadow);
background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
background-size: 100% 4px;
}
[ngTabPanel]:focus {
outline-offset: 4px;
outline: 4px dashed var(--bright-blue);
}
[ngTabPanel][inert] {
display: none;
}
Use [selectionMode]="'explicit'" for heavy content panels to avoid unnecessary rendering.
Arrange tabs vertically for interfaces like settings panels or navigation sidebars.
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs>
<div ngTabList orientation="vertical" selectedTab="movie">
<div ngTab value="movie">
<span class="material-symbols-outlined" translate="no" aria-hidden="true">videocam</span>
</div>
<div ngTab value="theatres">
<span class="material-symbols-outlined" translate="no" aria-hidden="true"
>theater_comedy</span
>
</div>
<div ngTab value="showtimes">
<span class="material-symbols-outlined" translate="no" aria-hidden="true">reviews</span>
</div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
height: 200px;
border-radius: 0.5rem;
border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
display: flex;
}
[ngTabList] {
padding: 0;
display: flex;
flex-direction: column;
list-style: none;
position: relative;
border-right: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
width: 80px;
cursor: pointer;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
display: grid;
place-items: center;
}
[ngTab][aria-selected='false']:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
border-top-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
.sliding-window {
height: 300%;
display: flex;
flex-direction: column;
transition: all 0.2s ease-in-out;
flex: 1;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateY(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateY(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateY(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
flex: 1;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs>
<div ngTabList class="material-tabs" orientation="vertical" selectedTab="movie">
<div ngTab value="movie">
<span class="material-symbols-outlined" translate="no" aria-hidden="true">videocam</span>
</div>
<div ngTab value="theatres">
<span class="material-symbols-outlined" translate="no" aria-hidden="true"
>theater_comedy</span
>
</div>
<div ngTab value="showtimes">
<span class="material-symbols-outlined" translate="no" aria-hidden="true">reviews</span>
</div>
<div class="bottom-border"></div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
font-family: var(--inter-font);
display: flex;;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
height: 200px;
border-radius: 0.5rem;
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
display: flex;
}
[ngTabList] {
padding: 0;
display: flex;
flex-direction: column;
list-style: none;
position: relative;
border-right: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
width: 80px;
cursor: pointer;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
display: grid;
place-items: center;
}
[ngTab]:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--bright-blue);
}
.bottom-border {
position: absolute;
pointer-events: none;
top: 0;
right: 0;
width: 3px;
height: calc(100% / 3);
background-color: var(--bright-blue);
transition: all 0.2s ease-in-out;
transform: translateY(0%);
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
transform: translateY(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
transform: translateY(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
transform: translateY(200%);
}
.sliding-window {
height: 300%;
display: flex;
flex-direction: column;
transition: all 0.2s ease-in-out;
flex: 1;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateY(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateY(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateY(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
flex: 1;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="retro-tabs">
<div ngTabList orientation="vertical" selectedTab="movie">
<div ngTab value="movie">
<span class="material-symbols-outlined" translate="no" aria-hidden="true">videocam</span>
</div>
<div ngTab value="theatres">
<span class="material-symbols-outlined" translate="no" aria-hidden="true"
>theater_comedy</span
>
</div>
<div ngTab value="showtimes">
<span class="material-symbols-outlined" translate="no" aria-hidden="true">reviews</span>
</div>
</div>
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Line 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Line 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Line 3</ng-template>
</div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
font-family: 'Press Start 2P';
--retro-button-color: var(--bright-blue);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngTabs] {
width: 500px;
display: flex;
}
[ngTabList] {
gap: 1rem;
display: flex;
flex-direction: column;
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem 1.5rem;
cursor: pointer;
text-align: center;
color: #000;
background-color: #fff;
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngTab]:focus,
[ngTab]:hover {
transform: translate(1px, 1px);
}
[ngTab]:focus {
outline-offset: 2px;
outline: 4px dashed var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}
[ngTab]:active,
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngTabPanel] {
flex: 1;
font-size: 1.5rem;
color: #000;
background-color: #fff;
padding: 1rem;
margin-left: 1rem;
min-width: 100px;
display: grid;
place-items: center;
box-shadow: var(--retro-flat-shadow);
background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
background-size: 100% 4px;
}
[ngTabPanel]:focus {
outline-offset: 4px;
outline: 4px dashed var(--bright-blue);
}
[ngTabPanel][inert] {
display: none;
}
Set [orientation]="'vertical'" on the tab list. Navigation changes to Up/Down arrow keys.
Use the ngTabContent directive on an ng-template to defer rendering tab panels until they're first shown.
<div ngTabs>
<ul ngTabList [(selectedTab)]="selectedTab">
<li ngTab value="tab1">Tab 1</li>
<li ngTab value="tab2">Tab 2</li>
</ul>
<div ngTabPanel value="tab1">
<ng-template ngTabContent>
<!-- This content only renders when Tab 1 is first shown -->
<app-heavy-component />
</ng-template>
</div>
<div ngTabPanel value="tab2">
<ng-template ngTabContent>
<!-- This content only renders when Tab 2 is first shown -->
<app-another-component />
</ng-template>
</div>
</div>
By default, content remains in the DOM after the panel is hidden. Set [preserveContent]="false" to remove content when the panel is deactivated.
Disable specific tabs to prevent user interaction. Control whether disabled tabs can receive keyboard focus.
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs>
<div ngTabList selectionMode="explicit" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes" disabled>Reviews</div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 0.75rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab][aria-disabled='true'] {
cursor: default;
color: color-mix(in srgb, var(--primary-contrast) 30%, transparent);
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngTab][aria-selected='false']:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab][aria-disabled='false'][aria-selected='false']:hover {
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
border-bottom-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="material-tabs">
<div ngTabList selectionMode="explicit" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes" disabled>Reviews</div>
<div class="bottom-border"></div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="movie">
<ng-template ngTabContent>Panel 1</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="theatres">
<ng-template ngTabContent>Panel 2</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="showtimes">
<ng-template ngTabContent>Panel 3</ng-template>
</div>
</div>
</div>
:host {
font-family: var(--inter-font);
display: flex;
justify-content: center;
}
[ngTabs] {
overflow: hidden;
width: 600px;
border-radius: 0.5rem;
background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}
[ngTabList] {
padding: 0;
display: flex;
list-style: none;
position: relative;
border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem 0;
cursor: pointer;
text-align: center;
color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}
[ngTab][aria-disabled='true'] {
cursor: default;
color: color-mix(in srgb, var(--primary-contrast) 30%, transparent);
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngTab]:focus {
outline-offset: -8px;
border-radius: 0.7rem;
outline: 2px solid var(--bright-blue);
}
[ngTab]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngTab][aria-selected='true'] {
color: var(--bright-blue);
}
.bottom-border {
position: absolute;
pointer-events: none;
left: 0;
bottom: 0;
height: 3px;
width: calc(100% / 3);
background-color: var(--bright-blue);
transition: all 0.2s ease-in-out;
transform: translateX(0%);
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
transform: translateX(200%);
}
.sliding-window {
width: 300%;
display: flex;
transition: all 0.2s ease-in-out;
}
[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
transform: translateX(-66.666%);
}
[ngTabPanel] {
display: grid;
place-items: center;
padding: 1rem;
min-height: 100px;
flex: 1;
}
[ngTabPanel]:focus {
outline-offset: -4px;
border-radius: 0.5rem;
outline: 2px solid var(--bright-blue);
}
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
<div ngTabs class="retro-tabs">
<div ngTabList selectionMode="explicit" selectedTab="movie">
<div ngTab value="movie">Movie</div>
<div ngTab value="theatres">Cast</div>
<div ngTab value="showtimes" disabled>Reviews</div>
</div>
<div ngTabPanel value="movie">
<ng-template ngTabContent>Line 1</ng-template>
</div>
<div ngTabPanel value="theatres">
<ng-template ngTabContent>Line 2</ng-template>
</div>
<div ngTabPanel value="showtimes">
<ng-template ngTabContent>Line 3</ng-template>
</div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
font-family: 'Press Start 2P';
--retro-button-color: var(--bright-blue);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngTabs] {
width: 600px;
}
[ngTabList] {
gap: 1rem;
display: flex;
}
[ngTab] {
flex: 1;
outline: none;
padding: 1rem;
cursor: pointer;
text-align: center;
color: #000;
background-color: #fff;
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngTab][aria-disabled='true'] {
cursor: default;
color: color-mix(in srgb, #000 30%, transparent);
}
[ngTab][aria-disabled='false'][aria-selected='false']:focus,
[ngTab][aria-disabled='false'][aria-selected='false']:hover {
transform: translate(1px, 1px);
}
[ngTab]:focus {
outline-offset: 2px;
outline: 4px dashed var(--bright-blue);
}
[ngTab][aria-disabled='false'][aria-selected='false']:hover {
background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}
[ngTab][aria-disabled='false']:active,
[ngTab][aria-selected='true'] {
color: var(--page-background);
background-color: var(--bright-blue);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngTabPanel] {
flex: 1;
font-size: 1.5rem;
color: #000;
background-color: #fff;
padding: 1rem;
margin-top: 1rem;
min-height: 100px;
display: grid;
place-items: center;
box-shadow: var(--retro-flat-shadow);
background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
background-size: 100% 4px;
}
[ngTabPanel]:focus {
outline-offset: 4px;
outline: 4px dashed var(--bright-blue);
}
[ngTabPanel][inert] {
display: none;
}
When [softDisabled]="true" on the tab list, disabled tabs can receive focus but cannot be activated. When [softDisabled]="false", disabled tabs are skipped during keyboard navigation.
The container directive that coordinates tab lists and panels.
This directive has no inputs or outputs. It serves as the root container for ngTabList, ngTab, and ngTabPanel directives.
The container for tab buttons that manages selection and keyboard navigation.
| Property | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'horizontal' | Tab list layout direction |
wrap | boolean | false | Whether keyboard navigation wraps from last to first tab |
softDisabled | boolean | true | When true, disabled tabs are focusable but not activatable |
selectionMode | 'follow' | 'explicit' | 'follow' | Whether tabs activate on focus or require explicit activation |
selectedTab | any | — | The value of the currently selected tab (supports two-way binding) |
An individual tab button.
| Property | Type | Default | Description |
|---|---|---|---|
value | any | — | Required. Unique value for this tab |
disabled | boolean | false | Disables this tab |
| Property | Type | Description |
|---|---|---|
selected | Signal<boolean> | Whether the tab is currently selected |
active | Signal<boolean> | Whether the tab currently has focus |
The content panel associated with a tab.
| Property | Type | Default | Description |
|---|---|---|---|
value | any | — |
Required. Must match the value of the associated tab |
preserveContent | boolean | true | Whether to keep panel content in DOM after deactivation |
| Property | Type | Description |
|---|---|---|
visible | Signal<boolean> | Whether the panel is currently visible |
A structural directive for lazy rendering tab panel content.
This directive has no inputs, outputs, or methods. Apply it to an ng-template element inside a tab panel:
<div ngTabPanel value="tab1">
<ng-template ngTabContent>
<!-- Content here is lazily rendered -->
</ng-template>
</div>
Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/aria/tabs