Documentation

Documentation - Tabs.

Découvrez Marssel : un framework CSS intelligent, configuration minimale, conçu pour des interfaces rapides et une expérience développeur simplifiée.

Tabs

Le système de tabs de Marssel offre une solution complète, accessible et performante pour créer des interfaces à onglets sans JavaScript manuel.

Configuration de base

Le TabsManager est automatiquement initialisé :

const marssel = new Marssel();
// Le TabsManager est prêt à l'emploi

Structure HTML

Aperçu

Contenu 1

Premier onglet.

Contenu 2

Deuxième onglet.

Contenu 3

Troisième onglet.

<div class="tabs-container" data-tabs="mon-groupe">
    <!-- 1. Inputs radio cachés -->
    <input type="radio" id="tab1" name="mes-tabs" class="tab-radio" checked>
    <input type="radio" id="tab2" name="mes-tabs" class="tab-radio">
    <input type="radio" id="tab3" name="mes-tabs" class="tab-radio">

    <!-- 2. Navigation des onglets -->
    <div class="tabs-nav---[d-[flex]+mb-[20px]+border-b-[1px_solid_e0e0e0]]">
        <label for="tab1"
            class="tab-label---[p-[15px]+cursor-[pointer]+c-[666]+border-b-[3px_solid_transparent]+transition-[all_0.3s]]">
            Onglet 1
        </label>
        <label for="tab2" class="tab-label">
            Onglet 2
        </label>
        <label for="tab3" class="tab-label">
            Onglet 3
        </label>
    </div>

    <!-- 3. Contenu des panels -->
    <div class="tabs-content">
        <div class="tab-panel---[p-[20px]+border-[1px_solid_e0e0e0]+rounded-[5px]]">
            <h4>Contenu 1</h4>
            <p>Premier onglet.</p>
        </div>
        <div class="tab-panel">
            <h4>Contenu 2</h4>
            <p>Deuxième onglet.</p>
        </div>
        <div class="tab-panel">
            <h4>Contenu 3</h4>
            <p>Troisième onglet.</p>
        </div>
    </div>
</div>

Règles importantes

✅ À faire :
- Chaque input radio doit avoir un ID unique
- Tous les radios d'un groupe doivent avoir le même attribut name
- Les labels doivent avoir un attribut for correspondant à l'ID du radio
- L'ordre des panels doit correspondre à l'ordre des radios
- Ajouter checked au premier radio pour l'afficher par défaut

Configuration

Attributs data disponibles :

Attribut                       Type                                     Défaut       Description
----------------------------------------------------------------------------------------------------------------------
data-tabs                       string                                  auto-généré  Identifiant unique du groupe
data-tabs-orientation           horizontal | vertical                   horizontal   Orientation des onglets
data-tabs-preset                default | pills | minimal | bordered    default      Style prédéfini
data-tabs-animated              true | false                            true         Active les animations
data-tabs-responsive            true | false                            true         Mode responsive
data-tabs-mobile-breakpoint     string                                  768px        Point de rupture mobile
data-tabs-active-color          color                                   #0066cc      Couleur de l'onglet actif
data-tabs-active-border-width   string                                  3px          Épaisseur de la bordure active
data-tabs-keyboard              true | false                            true         Active la navigation clavier

Exemple avec la configuration complète

<div class="tabs-container"
    data-tabs="advanced-tabs"
    data-tabs-orientation="vertical"
    data-tabs-preset="pills"
    data-tabs-animated="true"
    data-tabs-responsive="true"
    data-tabs-mobile-breakpoint="768px"
    data-tabs-active-color="#10b981 | red | var(--theme-primary)"
    data-tabs-active-border-width="4px"
    data-tabs-keyboard="true"
    id="my-tabs">
    <!-- Structure des tabs -->
</div>

Presets de style : Default

Style standard avec bordure inférieure ayant pour caractéristiques :

  • Bordure inférieure 3px
  • Couleur bleue (#0066cc)
  • Fond transparent
  • Padding 15px

<div class="tabs-container" data-tabs-preset="default">
    <!-- Bordure inférieure sur l'onglet actif -->
</div>

Presets de style : Pills.

Onglets en forme de pilules arrondies ayant pour caractéristiques :

  • Border-radius: 20px
  • Fond gris clair (#f0f0f0)
  • Pas de bordure
  • Padding 10px 20px

<div class="tabs-container" data-tabs-preset="pills">
    <!-- Onglets arrondis avec fond -->
</div>

Presets de style : Minimal

Style minimaliste épuré ayant pour caractéristiques :

  • Bordure fine 2px
  • Couleur neutre (#333)
  • Fond transparent
  • Padding 10px 15px

<div class="tabs-container" data-tabs-preset="bordered">
    <!-- Bordures sur tous les côtés -->
</div>

Presets de style : Bordered

Onglets avec bordures complètes ayant pour caractéristiques :

  • Bordure complète 1px
  • Fond blanc
  • Bordure active 2px
  • Padding 12px 20px

<div class="tabs-container"
    data-tabs-preset="pills"
    data-tabs-active-color="#10b981">
    <!-- Pills vert émeraude -->
</div>

API JavaScript : Méthodes de navigation.

Active un onglet spécifique par son index (0-based) avec activateTab(groupId, index) :

// Activer le 2e onglet
marssel.tabsManager.activateTab('mon-groupe', 1);

/*Paramètres :

groupId (string) : Identifiant du groupe de tabs
index (number) : Index de l'onglet (commence à 0)

Retour : boolean - true si succès, false sinon */

Active l'onglet suivant (boucle au début si à la fin) avec nextTab(groupId) :

marssel.tabsManager.nextTab('mon-groupe');

//Retour : boolean

Active l'onglet précédent (boucle à la fin si au début) avec previousTab(groupId) :

marssel.tabsManager.previousTab('mon-groupe');

//Retour : boolean

API JavaScript : Méthodes d'information

Récupère l'objet de l'onglet actuellement actif avec getActiveTab(groupId) :

const activeTab = marssel.tabsManager.getActiveTab('mon-groupe');
console.log(activeTab);
// {
//   radio: HTMLInputElement,
//   label: HTMLLabelElement,
//   panel: HTMLDivElement,
//   index: 0,
//   id: 'tab1'
// }

// Retour : Object | null

Récupère l'index de l'onglet actif avec getActiveIndex(groupId) :

const index = marssel.tabsManager.getActiveIndex('mon-groupe');
console.log(index); // 0, 1, 2, etc.

//Retour : number - Index de l'onglet actif, ou -1 si aucun

Récupère la liste de tous les groupes de tabs initialisés avec getAllGroups() :

const groups = marssel.tabsManager.getAllGroups();
console.log(groups); // ['groupe-1', 'groupe-2', ...]

//Retour : string[]

Récupère l'objet complet d'un groupe de tabs avec getGroup(groupId) :

const group = marssel.tabsManager.getGroup('mon-groupe');
console.log(group);
// {
//   id: 'mon-groupe',
//   container: HTMLDivElement,
//   tabs: [...],
//   config: {...},
//   groupName: 'mes-tabs',
//   initialized: true
// }

// Retour : Object | null

API JavaScript : Méthodes de contrôle

Désactive un onglet (le rend non cliquable) avec disableTab(groupId, index) :

marssel.tabsManager.disableTab('mon-groupe', 2);

//Effet :

//Input radio disabled = true
//Label opacity: 0.5 et pointer-events: none

//Retour : Boolean

Réactive un onglet précédemment désactivé avec enableTab(groupId, index) :

marssel.tabsManager.enableTab('mon-groupe', 2);

//Retour : boolean

Détruit un groupe de tabs et nettoie toutes les ressources avec destroy(groupId) :

marssel.tabsManager.destroy('mon-groupe');

//Actions effectuées :

//Suppression des event listeners
//Nettoyage des styles CSS
//Suppression du groupe de la Map

//Retour : boolean

Nettoie complètement le TabsManager (tous les groupes) avec cleanup() :

marssel.tabsManager.cleanup();

// Attention : Détruit tous les groupes et déconnecte l'observateur.

API JavaScript : Événements

Émis lors du changement d'onglet avec marssel:tab:change :

document.addEventListener('marssel:tab:change', (event) => {
    const {
        groupId,         // ID du groupe
        activeIndex,     // Index de l'onglet activé
        activeTab,       // Input radio actif
        activeLabel,     // Label actif
        activePanel,     // Panel actif
        previousIndex    // Index précédent
    } = event.detail;

    console.log(`Changement dans ${groupId}: ${previousIndex} → ${activeIndex}`);
});

Propriétés de event.detail :

| Propriété       | Type             | Description                                 |
|-----------------|--------------------|---------------------------------------------|
| groupId         | string             | Identifiant du groupe                       |
| activeIndex     | number             | Index de l'onglet activé (0-based)          |
| activeTab       | HTMLInputElement   | Élément <input> radio actif                 |
| activeLabel     | HTMLLabelElement   | Label de l'onglet actif                     |
| activePanel     | HTMLDivElement     | Panel de contenu actif                      |
| previousIndex   | number             | Index de l'onglet précédent                 |

Chargement lazy du contenu :

document.addEventListener('marssel:tab:change', async (e) => {
    const { activePanel, groupId } = e.detail;

    if (groupId === 'lazy-tabs' && activePanel.dataset.loaded !== 'true') {
        const content = await fetchContent(activePanel.dataset.url);
        activePanel.innerHTML = content;
        activePanel.dataset.loaded = 'true';
    }
});

Analytics :

document.addEventListener('marssel:tab:change', (e) => {
    gtag('event', 'tab_change', {
        group_id: e.detail.groupId,
        tab_index: e.detail.activeIndex
    });
});

Synchronisation entre groupes :

document.addEventListener('marssel:tab:change', (e) => {
    if (e.detail.groupId === 'group-1') {
        marssel.tabsManager.activateTab('group-2', e.detail.activeIndex);
    }
});

API JavaScript : Accessibilité

Le TabsManager gère automatiquement la navigation au clavier :

| Touche         | Action                          |
|----------------|--------------------------------|
| → / ↓          | Onglet suivant                 |
| ← / ↑          | Onglet précédent               |
| Home           | Premier onglet                 |
| End            | Dernier onglet                 |
| Enter / Space  | Activer l'onglet focusé        |
| Tab            | Sortir du groupe de tabs       |

Le TabsManager met à jour automatiquement les attributs ARIA :

<!-- Label de l'onglet actif -->
<label for="tab1" aria-selected="true" tabindex="0">
    Onglet 1
</label>

<!-- Panel actif -->
<div class="tab-panel" aria-hidden="false">
    Contenu visible
</div>

<!-- Panel inactif -->
<div class="tab-panel" aria-hidden="true" style="display: none;">
    Contenu masqué
</div>

Responsive Design

Avec data-tabs-responsive='true', les tabs basculent automatiquement en mode vertical sur mobile :

<div class="tabs-container"
        data-tabs-responsive="true"
        data-tabs-mobile-breakpoint="768px">
    <!-- Sur &lt; 768px : vertical -->
    <!-- Sur &gt;= 768px : horizontal -->
</div>

Comportement mobile sur Desktop (>= 768px) :

.tabs-nav {
    flex-direction: row;
    border-bottom: 1px solid #e0e0e0;
}

.tab-label {
    border-bottom: 3px solid transparent;
}

#tab1:checked ~ .tabs-nav label[for="tab1"] {
    border-bottom: 3px solid #0066cc;
}

Comportement mobile sur Mobile (< 768px) :

.tabs-nav {
    flex-direction: column;
}

.tab-label {
    border-left: 3px solid transparent;
    border-bottom: 1px solid #e0e0e0;
}

#tab1:checked ~ .tabs-nav label[for="tab1"] {
    border-left: 3px solid #0066cc;
    border-bottom: 1px solid #e0e0e0;
}

Personnalisation responsive :

<!-- Navigation responsive avec Marssel classes -->
<div class="tabs-nav---[d-[flex]+md--flex-direction-[column]+gap-[10px]]">
    <!-- Sur tablette/mobile : colonne -->
    <!-- Sur desktop : ligne -->
</div>

Exemples avancés

Tabs avec icônes :

Aperçu
Contenu accueil
Contenu paramètres
<div class="tabs-container" data-tabs="icon-tabs">
    <input type="radio" id="icon1" name="icons" class="tab-radio" checked>
    <input type="radio" id="icon2" name="icons" class="tab-radio">

    <div class="tabs-nav---[d-[flex]+mb-[20px]]">
        <label for="icon1"
                class="tab-label---[d-[flex]+flex-direction-[column]+items-[center]+gap-[8px]+p-[15px]]">
            <span class="fs-[24px]">🏠</span>
            <span>Accueil</span>
        </label>
        <label for="icon2" class="tab-label">
            <span class="fs-[24px]">⚙️</span>
            <span>Paramètres</span>
        </label>
    </div>

    <div class="tabs-content">
        <div class="tab-panel---[p-[20px]]">Contenu accueil</div>
        <div class="tab-panel">Contenu paramètres</div>
    </div>
</div>

Tabs avec badges

Aperçu
5 nouveaux messages
2 notifications
<div class="tabs-container" data-tabs="badge-tabs">
    <input type="radio" id="msg" name="badges" class="tab-radio" checked>
    <input type="radio" id="notif" name="badges" class="tab-radio">

    <div class="tabs-nav---[d-[flex]+mb-[20px]]">
        <label for="msg"
                class="tab-label---[flex-[1]+p-[15px]+pos-[relative]+text-align-[center]]">
            Messages
            <span class="badge---[pos-[absolute]+top-[8px]+right-[8px]+bg-[D41212]+c-[white]+w-[20px]+h-[20px]+rounded-[50%]+fs-[12px]+d-[flex]+items-[center]+justify-[center]]">
                5
            </span>
        </label>
        <label for="notif" class="tab-label">
            Notifications
            <span class="badge---[pos-[absolute]+top-[8px]+right-[8px]+bg-[0B63F3]+c-[white]+w-[20px]+h-[20px]+rounded-[50%]+fs-[12px]+d-[flex]+items-[center]+justify-[center]]">
                2
            </span>
        </label>
    </div>

    <div class="tabs-content">
        <div class="tab-panel---[p-[20px]]">5 nouveaux messages</div>
        <div class="tab-panel">2 notifications</div>
    </div>
</div>

Tabs verticaux

Aperçu
Configuration générale
Paramètres de sécurité
<div class="tabs-container"
        data-tabs="vertical-tabs"
        data-tabs-orientation="vertical"
        id="vertical-container"
        style="min-height: 300px;">

    <input type="radio" id="v1" name="vertical" class="tab-radio" checked>
    <input type="radio" id="v2" name="vertical" class="tab-radio">

    <div class="tabs-nav---[w-[200px]+bg-[f9f9f9]]">
        <label for="v1" class="tab-label---[p-[15px]+d-[block]]">
            Configuration
        </label>
        <label for="v2" class="tab-label">
            Sécurité
        </label>
    </div>

    <div class="tabs-content---[flex-[1]+p-[20px]]">
        <div class="tab-panel">Configuration générale</div>
        <div class="tab-panel">Paramètres de sécurité</div>
    </div>
</div>

Tabs imbriqués

Aperçu

Section 1

Contenu Sub A
Contenu Sub B
Section 2
<div class="tabs-container" data-tabs="parent-tabs">
    <input type="radio" id="p1" name="parent" class="tab-radio" checked>
    <input type="radio" id="p2" name="parent" class="tab-radio">

    <div class="tabs-nav---[d-[flex]+mb-[20px]]">
        <label for="p1" class="tab-label">Section 1</label>
        <label for="p2" class="tab-label">Section 2</label>
    </div>

    <div class="tabs-content">
        <div class="tab-panel---[p-[20px]]">
            <h4 class="mb-[20px]">Section 1</h4>
            <div class="tabs-container" data-tabs="nested-tabs">
                <input type="radio" id="n1" name="nested" class="tab-radio" checked>
                <input type="radio" id="n2" name="nested" class="tab-radio">

                <div class="tabs-nav---[d-[flex]+mb-[15px]]">
                    <label for="n1" class="tab-label---[p-[10px]+fs-[14px]]">Sub A</label>
                    <label for="n2" class="tab-label---[p-[10px]+fs-[14px]]">Sub B</label>
                </div>

                <div class="tabs-content">
                    <div class="tab-panel---[p-[15px]]">Contenu Sub A</div>
                    <div class="tab-panel">Contenu Sub B</div>
                </div>
            </div>
        </div>

        <div class="tab-panel">Section 2</div>
    </div>
</div>

Chargement lazy :

Aperçu
Contenu pré-chargé
Chargement...
<div class="tabs-container" data-tabs="lazy-tabs">
    <input type="radio" id="l1" name="lazy" class="tab-radio" checked>
    <input type="radio" id="l2" name="lazy" class="tab-radio">

    <div class="tabs-nav---[d-[flex]+mb-[20px]]">
        <label for="l1" class="tab-label">Chargé</label>
        <label for="l2" class="tab-label">Lazy</label>
    </div>

    <div class="tabs-content">
        <div class="tab-panel---[p-[20px]]" data-loaded="true">
            Contenu pré-chargé
        </div>
        <div class="tab-panel---[p-[20px]]" data-lazy-url="/api/content">
            <div class="text-align-[center]">Chargement...</div>
        </div>
    </div>
</div>

<script>
document.addEventListener('marssel:tab:change', async (e) => {
    const panel = e.detail.activePanel;

    if (panel && panel.dataset.lazyUrl && !panel.dataset.loaded) {
        const response = await fetch(panel.dataset.lazyUrl);
        const content = await response.text();
        panel.innerHTML = content;
        panel.dataset.loaded = 'true';
    }
});
</script>

Contrôle programmatique :

Aperçu
Contenu du premier onglet.
Contenu du deuxième onglet.
Contenu du troisième onglet.
<div class="mb-[20px]">
    <button onclick="marssel.tabsManager.previousTab('control-tabs')"
            class="px-[15px] py-[8px] bg-[0B63F3] c-[white] rounded-[5px]">
        ← Précédent
    </button>
    <button onclick="marssel.tabsManager.nextTab('control-tabs')"
            class="px-[15px] py-[8px] bg-[0B63F3] c-[white] rounded-[5px]">
        Suivant →
    </button>
    <button onclick="marssel.tabsManager.disableTab('control-tabs', 2)"
            class="px-[15px] py-[8px] bg-[D41212] c-[white] rounded-[5px]">
        Désactiver Tab 3
    </button>
</div>

<div class="tabs-container" data-tabs="control-tabs">
    <input type="radio" id="c1" name="control" class="tab-radio" checked>
    <input type="radio" id="c2" name="control" class="tab-radio">
    <input type="radio" id="c3" name="control" class="tab-radio">

    <div class="tabs-nav---[d-[flex]+mb-[20px]]">
        <label for="c1" class="tab-label">Tab 1</label>
        <label for="c2" class="tab-label">Tab 2</label>
        <label for="c3" class="tab-label">Tab 3 (Désactivable)</label>
    </div>

    <div class="tabs-content">
        <div class="tab-panel---[p-[20px]]">
            Contenu du premier onglet.
        </div>
        <div class="tab-panel">
            Contenu du deuxième onglet.
        </div>
        <div class="tab-panel">
            Contenu du troisième onglet.
        </div>
    </div>
</div>