Tabs (Navigasi Tab)
Tabs mengorganisasi konten yang berada pada level hierarki yang sama ke dalam panel terpisah yang dapat dialihkan secara cepat.
Pratinjau Komponen
Penggunaan CSS Murni
<!-- Tab Garis Bawah -->
<div class="mjl-tabs">
<ul class="mjl-tab-list" role="tablist">
<li role="presentation">
<button class="mjl-tab mjl-tab--active" role="tab" aria-selected="true">Tab 1</button>
</li>
<li role="presentation">
<button class="mjl-tab" role="tab" aria-selected="false">Tab 2</button>
</li>
</ul>
</div>
<!-- Tab Pil Kapsul -->
<div class="mjl-tabs mjl-tabs--pill">
<ul class="mjl-tab-list">
<li><button class="mjl-tab mjl-tab--active">Tab 1</button></li>
<li><button class="mjl-tab">Tab 2</button></li>
</ul>
</div>
Penggunaan di Berbagai Teknologi
Salin contoh sesuai teknologi proyek Anda. Semua versi memakai class dari @majalengka/css. Komponen siap pakai tersedia di @majalengka/vue; untuk React, Angular, Laravel, dan PHP, contoh di bawah sudah berupa pembungkus kecil yang bisa langsung disalin ke proyek.
<div class="mjl-tabs">
<div class="mjl-tab-list" role="tablist" aria-label="Informasi layanan">
<button type="button" role="tab" id="tab-syarat" class="mjl-tab mjl-tab--active" aria-selected="true" aria-controls="panel-syarat">Persyaratan</button>
<button type="button" role="tab" id="tab-biaya" class="mjl-tab" aria-selected="false" aria-controls="panel-biaya" tabindex="-1">Biaya</button>
</div>
<div role="tabpanel" id="panel-syarat" class="mjl-tab-panel mjl-tab-panel--active" aria-labelledby="tab-syarat" tabindex="0">Fotokopi KTP dan KK.</div>
<div role="tabpanel" id="panel-biaya" class="mjl-tab-panel" aria-labelledby="tab-biaya" tabindex="0" hidden>Gratis.</div>
</div>
<script setup lang="ts">
import { ref } from 'vue';
import { MjlTabs } from '@majalengka/vue';
const aktif = ref('syarat');
const tabs = [
{ value: 'syarat', label: 'Persyaratan' },
{ value: 'biaya', label: 'Biaya' },
];
</script>
<template>
<MjlTabs v-model="aktif" :items="tabs" label="Informasi layanan">
<template #syarat>Fotokopi KTP dan KK.</template>
<template #biaya>Gratis.</template>
</MjlTabs>
</template>
import { useId, useState, type KeyboardEvent, type ReactNode } from 'react';
export function MjlTabs({ tabs }: { tabs: { value: string; label: string; content: ReactNode }[] }) {
const [active, setActive] = useState(tabs[0].value);
const id = useId();
function onKeyDown(e: KeyboardEvent) {
const i = tabs.findIndex((t) => t.value === active);
const next = e.key === 'ArrowRight' ? (i + 1) % tabs.length : e.key === 'ArrowLeft' ? (i - 1 + tabs.length) % tabs.length : -1;
if (next < 0) return;
setActive(tabs[next].value);
document.getElementById(`${id}-tab-${tabs[next].value}`)?.focus();
}
return (
<div className="mjl-tabs">
<div className="mjl-tab-list" role="tablist" onKeyDown={onKeyDown}>
{tabs.map((t) => (
<button key={t.value} type="button" role="tab" id={`${id}-tab-${t.value}`} aria-controls={`${id}-panel-${t.value}`}
className={`mjl-tab ${t.value === active ? 'mjl-tab--active' : ''}`} aria-selected={t.value === active}
tabIndex={t.value === active ? 0 : -1} onClick={() => setActive(t.value)}>{t.label}</button>
))}
</div>
{tabs.map((t) => (
<div key={t.value} role="tabpanel" id={`${id}-panel-${t.value}`} aria-labelledby={`${id}-tab-${t.value}`} tabIndex={0}
className={`mjl-tab-panel ${t.value === active ? 'mjl-tab-panel--active' : ''}`} hidden={t.value !== active}>{t.content}</div>
))}
</div>
);
}
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-tab-layanan',
standalone: true,
template: `
<div class="mjl-tabs">
<div class="mjl-tab-list" role="tablist" aria-label="Informasi layanan" (keydown)="onKey($event)">
@for (t of tabs; track t.value) {
<button type="button" role="tab" [id]="'tab-' + t.value" class="mjl-tab" [class.mjl-tab--active]="t.value === active()"
[attr.aria-selected]="t.value === active()" [attr.aria-controls]="'panel-' + t.value"
[tabIndex]="t.value === active() ? 0 : -1" (click)="active.set(t.value)">{{ t.label }}</button>
}
</div>
@for (t of tabs; track t.value) {
<div role="tabpanel" [id]="'panel-' + t.value" class="mjl-tab-panel" [class.mjl-tab-panel--active]="t.value === active()"
[attr.aria-labelledby]="'tab-' + t.value" tabindex="0" [hidden]="t.value !== active()">{{ t.content }}</div>
}
</div>
`,
})
export class TabLayanan {
tabs = [
{ value: 'syarat', label: 'Persyaratan', content: 'Fotokopi KTP dan KK.' },
{ value: 'biaya', label: 'Biaya', content: 'Gratis.' },
];
active = signal('syarat');
onKey(e: KeyboardEvent) {
const i = this.tabs.findIndex((t) => t.value === this.active());
const step = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;
if (!step) return;
const next = this.tabs[(i + step + this.tabs.length) % this.tabs.length];
this.active.set(next.value);
document.getElementById('tab-' + next.value)?.focus();
}
}
{{-- Tab berbasis URL (?tab=...): tanpa JavaScript, cocok untuk halaman server-rendered --}}
@php($aktif = request('tab', 'syarat'))
<div class="mjl-tabs">
<nav class="mjl-tab-list" aria-label="Informasi layanan">
@foreach (['syarat' => 'Persyaratan', 'biaya' => 'Biaya'] as $kunci => $label)
<a href="?tab={{ $kunci }}" @class(['mjl-tab', 'mjl-tab--active' => $aktif === $kunci])
@if ($aktif === $kunci) aria-current="page" @endif>{{ $label }}</a>
@endforeach
</nav>
<div class="mjl-tab-panel mjl-tab-panel--active">
@include("layanan.tab-{$aktif}")
</div>
</div>
<?php
$tab = ['syarat' => 'Persyaratan', 'biaya' => 'Biaya'];
$aktif = array_key_exists($_GET['tab'] ?? '', $tab) ? $_GET['tab'] : 'syarat';
?>
<div class="mjl-tabs">
<nav class="mjl-tab-list" aria-label="Informasi layanan">
<?php foreach ($tab as $kunci => $label): ?>
<a href="?tab=<?= $kunci ?>" class="mjl-tab<?= $aktif === $kunci ? ' mjl-tab--active' : '' ?>"
<?= $aktif === $kunci ? 'aria-current="page"' : '' ?>><?= $label ?></a>
<?php endforeach; ?>
</nav>
<div class="mjl-tab-panel mjl-tab-panel--active">
<?php include __DIR__ . "/tab-{$aktif}.php"; ?>
</div>
</div>
Mengikuti pola WAI-ARIA Tabs: role="tablist"/tab/tabpanel, panah kiri/kanan dan Home/End untuk berpindah tab, dan hanya tab aktif yang masuk urutan Tab. Pakai variant="pill" untuk gaya pil.
Versi Laravel dan PHP memakai tautan ?tab= (dirender server, tanpa JavaScript), jadi tiap tab adalah halaman tersendiri dan ditandai aria-current="page", bukan pola role="tablist".