Tabs
Genel Bakış
Sekmeler, aynı anda yalnızca bir panelin görünebildiği katmanlı içerik bölümleri gösterir. Kullanıcılar sekme butonlarına tıklayarak veya sekme listesinde gezinmek için ok tuşları kullanarak paneller arasında geçiş yapar.
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
@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;
}
Kullanım
Sekmeler, kullanıcıların farklı görünümler veya kategoriler arasında geçiş yaptığı, ilişkili içeriği farklı bölümlere düzenlemek için iyi çalışır.
Sekmeleri kullanın:
- İlişkili içeriği farklı bölümlere düzenlerken
- Birden fazla kategoriye sahip ayar panelleri oluştururken
- Birden fazla konuya sahip dokümantasyon oluştururken
- Farklı görünümlere sahip kontrol panelleri uygularken
- Kullanıcıların bağlam değiştirmesi gerektiği içeriği gösterirken
Sekmelerden kaçının:
- Sıralı formlar veya sihirbazlar oluştururken (bir adım kalıbı kullanın)
- Sayfalar arasında gezinirken (yönlendirici navigasyonu kullanın)
- Tek içerik bölümleri gösterirken (sekmelere gerek yok)
- 7-8'den fazla sekme olduğunda (farklı bir yerleşim düşünün)
Özellikler
- Seçim modları - Sekmeler odaklandığında otomatik olarak etkinleşir veya manuel etkinleştirme gerektirir
- Klavye navigasyonu - Ok tuşları, Home ve End ile verimli sekme navigasyonu
- Yön - Yatay veya dikey sekme listesi yerleşimi
- Tembel içerik - Sekme panelleri yalnızca ilk etkinleştirildiğinde oluşturulur
- Devre dışı sekmeler - Odak yönetimiyle bireysel sekmeleri devre dışı bırakın
- Odak modları - Dolaşan tabindex veya activedescendant odak stratejileri
- RTL desteği - Sağdan sola dil navigasyonu
Örnekler
Seçim odağı takip eder
Seçim odağı takip ettiğinde, ok tuşlarıyla gezindikçe sekmeler anında etkinleşir. Bu anında geri bildirim sağlar ve hafif içerik için iyi çalışır.
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
@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;
}
Bu davranışı etkinleştirmek için sekme listesinde [selectionMode]="'follow'" ayarlayın.
Manuel etkinleştirme
Manuel etkinleştirmede, ok tuşları seçili sekmeyi değiştirmeden sekmeler arasında odak taşır. Kullanıcılar odaklanan sekmeyi etkinleştirmek için Boşluk veya Enter'a basar.
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
@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;
}
Gereksiz render'ı önlemek için ağır içerik panellerinde [selectionMode]="'explicit'" kullanın.
Dikey sekmeler
Ayar panelleri veya navigasyon kenar çubukları gibi arayüzler için sekmeleri dikey olarak düzenleyin.
app.ts
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 {}
app.html
<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>
app.css
@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);
}
app.ts
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 {}
app.html
<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>
app.css
@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);
}
app.ts
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 {}
app.html
<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>
app.css
@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;
}
Sekme listesinde [orientation]="'vertical'" ayarlayın. Navigasyon Yukarı/Aşağı ok tuşlarına değişir.
Tembel içerik render'ı
Sekme panellerinin ilk gösterilene kadar render edilmesini ertelemek için bir ng-template üzerinde ngTabContent yönergesini kullanın.
<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>
<!-- Bu içerik yalnızca Sekme 1 ilk gösterildiğinde render edilir -->
<app-heavy-component />
</ng-template>
</div>
<div ngTabPanel value="tab2">
<ng-template ngTabContent>
<!-- Bu içerik yalnızca Sekme 2 ilk gösterildiğinde render edilir -->
<app-another-component />
</ng-template>
</div>
</div>
Varsayılan olarak, panel gizlendikten sonra içerik DOM'da kalır. Panel devre dışı bırakıldığında içeriği DOM'dan kaldırmak için [preserveContent]="false" ayarlayın.
Devre dışı sekmeler
Kullanıcı etkileşimini engellemek için belirli sekmeleri devre dışı bırakın. Devre dışı sekmelerin klavye odağı alıp alamayacağını kontrol edin.
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
: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);
}
app.ts
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 {}
app.html
<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>
app.css
@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;
}
Sekme listesinde [softDisabled]="true" olduğunda, devre dışı sekmeler odak alabilir ancak etkinleştirilemez. [softDisabled]="false" olduğunda, devre dışı sekmeler klavye navigasyonu sırasında atlanır.
Test etme
Angular Aria, sekme bileşenlerini test etmek için bileşen harness'leri sağlar. İşte bir bileşen testinde harness'lerin nasıl kullanılacağına dair bir örnek:
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ComponentHarness, HarnessLoader} from '@angular/cdk/testing';
import {TabsHarness} from '@angular/aria/tabs/testing';
import {MyTabsComponent} from './my-tabs'; // Sizin bileşeniniz
// Sekme paneli içindeki içeriği sorgulamaya yardımcı olan basit bir harness
class TestContentHarness extends ComponentHarness {
static hostSelector = '.test-content';
async getText(): Promise<string> {
return (await this.host()).text();
}
}
describe('MyTabsComponent', () => {
let fixture: ComponentFixture<MyTabsComponent>;
let loader: HarnessLoader;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [MyTabsComponent],
});
fixture = TestBed.createComponent(MyTabsComponent);
await fixture.whenStable();
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('should switch tabs and scope panel queries', async () => {
const tabs = await loader.getHarness(TabsHarness);
// Tüm sekmeleri al
const tabItems = await tabs.getTabs();
expect(tabItems.length).toBe(3);
// İlk seçimi doğrula
expect(await tabItems[0].isSelected()).toBe(true);
expect(await tabItems[1].isSelected()).toBe(false);
// Etkin sekme paneli içindeki içeriği sorgula
// TabHarness, sorguları otomatik olarak ilişkili paneliyle sınırlandırır
const content = await tabItems[0].getHarness(TestContentHarness);
expect(await content.getText()).toBe('Content 1');
// İkinci sekmeye geç
await tabItems[1].select();
// Seçimin güncellendiğini doğrula
expect(await tabItems[0].isSelected()).toBe(false);
expect(await tabItems[1].isSelected()).toBe(true);
});
});
API'ler
Tabs
Sekme listeleri ve panelleri koordine eden kapsayıcı yönerge.
Bu yönergenin girişi veya çıktısı yoktur. ngTabList, ngTab ve ngTabPanel yönergeleri için kök kapsayıcı görevi görür.
TabList
Seçim ve klavye navigasyonunu yöneten sekme butonları için kapsayıcı.
Girişler
| Property | Type | Default | Description |
|---|---|---|---|
orientation |
'horizontal' | 'vertical' |
'horizontal' |
Sekme listesi yerleşim yönü |
wrap |
boolean |
true |
Klavye navigasyonunun son sekmeden ilk sekmeye sarılıp sarılmadığı |
softDisabled |
boolean |
true |
true olduğunda, devre dışı sekmeler odaklanabilir ancak etkinleştirilemez |
selectionMode |
'follow' | 'explicit' |
'follow' |
Sekmelerin odaklandığında mı yoksa açık etkinleştirme mi gerektirdiği |
focusMode |
'roving' | 'activedescendant' |
'roving' |
Odak yönetimi stratejisi |
selectedTab |
any |
— | Şu anda seçili sekmenin değeri (iki yönlü bağlama destekler) |
Tab
Bireysel bir sekme butonu.
Girişler
| Property | Type | Default | Description |
|---|---|---|---|
value |
any |
— | Zorunlu. Bu sekme için benzersiz değer |
disabled |
boolean |
false |
Bu sekmeyi devre dışı bırakır |
Sinyaller
| Property | Type | Description |
|---|---|---|
selected |
Signal<boolean> |
Sekmenin şu anda seçili olup olmadığı |
active |
Signal<boolean> |
Sekmenin şu anda odakta olup olmadığı |
TabPanel
Bir sekmeyle ilişkili içerik paneli.
Girişler
| Property | Type | Default | Description |
|---|---|---|---|
value |
any |
— | Zorunlu. İlişkili sekmenin value değeri ile eşleşmelidir |
preserveContent |
boolean |
true |
Devre dışı bırakılmadan sonra panel içeriğinin DOM'da tutulup tutulmayacağı |
Sinyaller
| Property | Type | Description |
|---|---|---|
visible |
Signal<boolean> |
Panelin şu anda görünür olup olmadığı |
TabContent
Sekme panel içeriğini tembel render etmek için yapısal yönerge.
Bu yönergenin girişi, çıktısı veya yöntemi yoktur. Bir sekme paneli içindeki ng-template elemanına uygulayın:
<div ngTabPanel value="tab1">
<ng-template ngTabContent>
<!-- Buradaki içerik tembel olarak render edilir -->
</ng-template>
</div>