Changer le thème

Construire un squelette admin avec shadcn/ui : Sidebar + Layout, bonnes pratiques

Easton editorial illustration: developer problem-solving desk

Pas à pas, construisez un squelette admin extensible — de la Sidebar à l’intégration Next.js Layout, avec du code prêt à l’emploi.


La semaine dernière, j’ai pris un projet de back-office admin — shadcn/ui m’est venu tout de suite en tête. J’avais déjà utilisé Ant Design et MUI ; personnaliser les styles restait pénible — soit des tonnes d’overrides, soit des contraintes imposées par le framework.

shadcn/ui, c’est autre chose. Modèle « Copy-paste » : le code est dans votre projet, vous modifiez comme vous voulez. Deux semaines d’usage plus tard, le constat est net. Surtout la Sidebar avec l’App Router Next.js : le squelette admin devient beaucoup plus clair.

Cet article rassemble ma pratique : partir de zéro pour un layout admin extensible.


1. Pourquoi shadcn/ui Sidebar ?

Commençons par les pièges que j’ai rencontrés.

Avec Ant Design Pro, le démarrage est rapide — mais à long terme ça se complique : modifier le style de la sidebar, c’est fouiller la doc ; une interaction personnalisée bute souvent sur les limites du framework. MUI pose le même type de problème : le theming est flexible, mais suppose Material Design.

Limites des approches classiques

Ant Design : fonctionnalités complètes, coût de personnalisation élevé. Arrondir les coins de la sidebar peut demander trois niveaux d’overrides CSS.

MUI : design system complet, courbe d’apprentissage raide. Styled Components — une semaine d’onboarding pour les nouveaux du équipe, en pratique.

Tout coder soi-même : contrôle total, mais une sidebar responsive avec accessibilité et navigation clavier ? Comptez au minimum trois jours.

La réponse de shadcn/ui

shadcn/ui emprunte une autre voie :

  • Modèle Copy-paste : le code du composant est dans votre projet, sans boîte noire
  • Fondation Radix UI : accessibilité intégrée — navigation clavier, attributs ARIA déjà gérés
  • Piloté par Tailwind CSS : le style ce sont des classes, vous modifiez sans guerre d’overrides

J’ai vu des équipes migrer de MUI vers shadcn/ui pour une raison simple : elles veulent du contrôle, pas un template clé en main.

Cas d’usage

Si vous travaillez sur :

  • Un back-office admin de taille moyenne
  • La console d’un produit SaaS
  • Un outil interne ou une plateforme ops

shadcn/ui Sidebar vaut le coup. Ce n’est pas un template admin complet, mais un squelette suffisamment flexible.


2. Architecture du composant Sidebar

Avant de coder, comprenons l’écosystème shadcn/ui Sidebar — la doc officielle est claire sur ce point, je résume.

Liste des composants clés

La Sidebar est un ensemble de composants aux rôles distincts :

SidebarProvider   // Contexte d'état, enveloppe toute l'app
Sidebar          // Conteneur de la barre latérale
SidebarHeader    // Zone fixe en haut, logo
SidebarContent   // Zone scrollable, menus
SidebarGroup     // Groupe de menu
SidebarMenu      // Liste de menu
SidebarMenuItem  // Entrée de menu
SidebarMenuButton // Bouton de menu (support Link)
SidebarFooter    // Zone fixe en bas, infos utilisateur
SidebarTrigger   // Bouton replier/déplier
SidebarInset     // Wrapper de la zone principale

Beaucoup de composants, mais la hiérarchie est simple :

SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   └── SidebarGroup
│   │       └── SidebarMenu
│   │           └── SidebarMenuItem
│   │               └── SidebarMenuButton
│   └── SidebarFooter
└── SidebarInset
    └── {children}

Gestion de l’état

L’état replié est géré par SidebarProvider. Deux modes :

Mode non contrôlé (recommandé) :

<SidebarProvider defaultOpen={true}>
  <Sidebar />
</SidebarProvider>

Mode contrôlé :

const [open, setOpen] = useState(true);

<SidebarProvider open={open} onOpenChange={setOpen}>
  <Sidebar />
</SidebarProvider>

En général, le mode non contrôlé suffit. Le mode contrôlé sert si vous devez piloter la Sidebar ailleurs (par ex. un interrupteur dans les paramètres utilisateur).

Design responsive

La Sidebar gère le responsive en interne :

  • Desktop : barre fixe à gauche, repli possible via SidebarTrigger
  • Mobile : devient un tiroir Sheet, ouvert au clic sur le Trigger

La logique est dans le composant — configurez SidebarProvider, le reste suit.


3. Intégration Next.js Layout en pratique

Les concepts posés, place à l’intégration dans le système Layout de Next.js.

3.1 Structure du projet

Je recommande les Route Groups Next.js pour organiser les layouts : pages à usages différents, layouts différents, URL inchangées.

app/
├── layout.tsx              # Root Layout (global)
├── (marketing)/            # Groupe marketing (Landing, About)
│   ├── layout.tsx          # Sans Sidebar
│   └── page.tsx            # Accueil
├── (dashboard)/            # Groupe back-office
│   ├── layout.tsx          # Layout avec Sidebar
│   ├── page.tsx            # Page Dashboard
│   ├── users/
│   │   └── page.tsx        # Gestion utilisateurs
│   └── settings/
│       └── page.tsx        # Paramètres système
└── (auth)/                 # Groupe authentification
    ├── layout.tsx          # Layout centré
    ├── login/
    │   └── page.tsx        # Connexion
    └── register/
        └── page.tsx        # Inscription

Avantages :

  1. Isolation des layouts : marketing sans Sidebar, back-office avec — séparation naturelle via Route Groups
  2. URLs sobres : (dashboard) n’apparaît pas dans l’URL, /users reste /users
  3. Extensible : nouveau groupe = nouveau dossier

3.2 Configuration du Root Layout

Le Root Layout est l’entrée de l’app — thème, polices, SidebarProvider s’y configurent.

// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { SidebarProvider } from "@/components/ui/sidebar";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Admin Dashboard",
  description: "Built with shadcn/ui and Next.js",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;html lang="fr-FR"&gt;
      &lt;body className={inter.className}&gt;
        &lt;SidebarProvider&gt;
          {children}
        &lt;/SidebarProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}

Note : SidebarProvider est dans le Root Layout, pas le Dashboard Layout. L’état de la Sidebar persiste entre pages (de /users à /settings, l’état replié ne se perd pas).

3.3 Implémentation du Dashboard Layout

Le Dashboard Layout est le cœur du back-office — la Sidebar s’y introduit.

// app/(dashboard)/layout.tsx
import { AppSidebar } from "@/components/app-sidebar";
import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar";
import { Separator } from "@/components/ui/separator";
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;&gt;
      &lt;AppSidebar /&gt;
      &lt;SidebarInset&gt;
        &lt;header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"&gt;
          &lt;SidebarTrigger className="-ml-1" /&gt;
          &lt;Separator orientation="vertical" className="mr-2 h-4" /&gt;
          &lt;Breadcrumb&gt;
            &lt;BreadcrumbList&gt;
              &lt;BreadcrumbItem className="hidden md:block"&gt;
                &lt;BreadcrumbLink href="/dashboard"&gt;
                  Administration
                &lt;/BreadcrumbLink&gt;
              &lt;/BreadcrumbItem&gt;
              &lt;BreadcrumbSeparator className="hidden md:block" /&gt;
              &lt;BreadcrumbItem&gt;
                &lt;BreadcrumbPage&gt;Vue d'ensemble&lt;/BreadcrumbPage&gt;
              &lt;/BreadcrumbItem&gt;
            &lt;/BreadcrumbList&gt;
          &lt;/Breadcrumb&gt;
        &lt;/header&gt;
        &lt;main className="flex-1 p-4 pt-6"&gt;{children}&lt;/main&gt;
      &lt;/SidebarInset&gt;
    &lt;/gt;
  );
}

Ce layout comprend :

  1. AppSidebar : sidebar personnalisée (section suivante)
  2. SidebarInset : wrapper du contenu principal, gère la largeur au repli
  3. Header : barre supérieure avec SidebarTrigger et fil d’Ariane
  4. Main : zone de contenu principal

3.4 Implémentation du composant AppSidebar

Implémentons la sidebar. Approche pilotée par configuration : le menu vit dans un fichier de config, le composant rend selon cette config.

D’abord la configuration de navigation :

// lib/navigation.ts
import {
  Home,
  Users,
  Settings,
  FileText,
  BarChart3,
  Shield,
} from "lucide-react";

export interface NavItem {
  title: string;
  href: string;
  icon: React.ComponentType&lt;{ className?: string }&gt;;
  badge?: string;
}

export const navConfig: NavItem[] = [
  {
    title: "Vue d'ensemble",
    href: "/dashboard",
    icon: Home,
  },
  {
    title: "Utilisateurs",
    href: "/users",
    icon: Users,
    badge: "12", // badge
  },
  {
    title: "Analytique",
    href: "/analytics",
    icon: BarChart3,
  },
  {
    title: "Contenu",
    href: "/content",
    icon: FileText,
  },
  {
    title: "Paramètres",
    href: "/settings",
    icon: Settings,
  },
  {
    title: "Permissions",
    href: "/permissions",
    icon: Shield,
  },
];

Puis AppSidebar :

// components/app-sidebar.tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { navConfig } from "@/lib/navigation";
import { Logo } from "@/components/logo";
import { UserNav } from "@/components/user-nav";

export function AppSidebar() {
  const pathname = usePathname();

  return (
    &lt;Sidebar&gt;
      &lt;SidebarHeader className="border-b border-border"&gt;
        &lt;Logo /&gt;
      &lt;/SidebarHeader&gt;

      &lt;SidebarContent&gt;
        &lt;SidebarGroup&gt;
          &lt;SidebarGroupLabel&gt;Navigation&lt;/SidebarGroupLabel&gt;
          &lt;SidebarGroupContent&gt;
            &lt;SidebarMenu&gt;
              {navConfig.map((item) =&gt; {
                const isActive = pathname === item.href;

                return (
                  &lt;SidebarMenuItem key={item.href}&gt;
                    &lt;SidebarMenuButton
                      asChild
                      isActive={isActive}
                      tooltip={item.title}
                    &gt;
                      &lt;Link href={item.href}&gt;
                        &lt;item.icon className="h-4 w-4" /&gt;
                        &lt;span&gt;{item.title}&lt;/span&gt;
                        {item.badge && (
                          &lt;span className="ml-auto text-xs bg-primary text-primary-foreground rounded-full px-2 py-0.5"&gt;
                            {item.badge}
                          &lt;/span&gt;
                        )}
                      &lt;/Link&gt;
                    &lt;/SidebarMenuButton&gt;
                  &lt;/SidebarMenuItem&gt;
                );
              })}
            &lt;/SidebarMenu&gt;
          &lt;/SidebarGroupContent&gt;
        &lt;/SidebarGroup&gt;
      &lt;/SidebarContent&gt;

      &lt;SidebarFooter className="border-t border-border"&gt;
        &lt;UserNav /&gt;
      &lt;/SidebarFooter&gt;
    &lt;/Sidebar&gt;
  );
}

Point clé : surlignage de route. usePathname() récupère le chemin courant, comparaison avec item.href — si match, isActive={true} sur SidebarMenuButton, le composant applique le style actif.

3.5 Menu multi-niveaux

Pour un menu à deux niveaux, enveloppez SidebarGroup avec Collapsible :

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ChevronDown } from "lucide-react";

// Dans SidebarMenu
&lt;Collapsible defaultOpen&gt;
  &lt;SidebarMenuItem&gt;
    &lt;CollapsibleTrigger asChild&gt;
      &lt;SidebarMenuButton&gt;
        &lt;Settings className="h-4 w-4" /&gt;
        &lt;span&gt;Paramètres système&lt;/span&gt;
        &lt;ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" /&gt;
      &lt;/SidebarMenuButton&gt;
    &lt;/CollapsibleTrigger&gt;
    &lt;CollapsibleContent&gt;
      &lt;SidebarMenuSub&gt;
        &lt;SidebarMenuSubItem&gt;
          &lt;SidebarMenuSubButton href="/settings/general"&gt;
            &lt;span&gt;Général&lt;/span&gt;
          &lt;/SidebarMenuSubButton&gt;
        &lt;/SidebarMenuSubItem&gt;
        &lt;SidebarMenuSubItem&gt;
          &lt;SidebarMenuSubButton href="/settings/security"&gt;
            &lt;span&gt;Sécurité&lt;/span&gt;
          &lt;/SidebarMenuSubButton&gt;
        &lt;/SidebarMenuSubItem&gt;
      &lt;/SidebarMenuSub&gt;
    &lt;/CollapsibleContent&gt;
  &lt;/SidebarMenuItem&gt;
&lt;/Collapsible&gt;

4. Fonctionnalités avancées

Le layout de base est en place — quelques ajouts utiles.

4.1 Contrôle d’accès (RBAC)

Beaucoup de back-offices affichent des menus selon le rôle. Simple : champ roles dans la config, filtrage au rendu.

Adapter la configuration :

// lib/navigation.ts
export interface NavItem {
  title: string;
  href: string;
  icon: React.ComponentType&lt;{ className?: string }&gt;;
  roles?: string[]; // rôles autorisés
}

export const navConfig: NavItem[] = [
  {
    title: "Vue d'ensemble",
    href: "/dashboard",
    icon: Home,
    // pas de roles — visible pour tous
  },
  {
    title: "Utilisateurs",
    href: "/users",
    icon: Users,
    roles: ["admin", "manager"], // admin et manager uniquement
  },
  {
    title: "Permissions",
    href: "/permissions",
    icon: Shield,
    roles: ["admin"], // admin uniquement
  },
];

Filtrer selon le rôle dans AppSidebar :

// components/app-sidebar.tsx
import { useAuth } from "@/hooks/use-auth"; // supposons un hook auth

export function AppSidebar() {
  const pathname = usePathname();
  const { user } = useAuth(); // utilisateur courant

  const filteredNav = navConfig.filter((item) =&gt; {
    if (!item.roles) return true; // pas de restriction
    return item.roles.some((role) =&gt; user?.roles?.includes(role));
  });

  return (
    &lt;Sidebar&gt;
      {/* ... */}
      &lt;SidebarMenu&gt;
        {filteredNav.map((item) =&gt; {
          // ...
        })}
      &lt;/SidebarMenu&gt;
      {/* ... */}
    &lt;/Sidebar&gt;
  );
}

Résultat : un utilisateur standard ne voit plus l’entrée « Permissions ».

4.2 Liens externes et séparateurs

Parfois la sidebar accueille des liens externes (doc, centre d’aide) ou des séparateurs entre groupes. shadcn/ui Sidebar le supporte :

&lt;SidebarGroup&gt;
  &lt;SidebarGroupLabel&gt;Fonctions principales&lt;/SidebarGroupLabel&gt;
  &lt;SidebarGroupContent&gt;
    &lt;SidebarMenu&gt;
      {/* entrées principales */}
    &lt;/SidebarMenu&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

&lt;SidebarGroup&gt;
  &lt;SidebarGroupLabel&gt;Aide et support&lt;/SidebarGroupLabel&gt;
  &lt;SidebarGroupContent&gt;
    &lt;SidebarMenu&gt;
      &lt;SidebarMenuItem&gt;
        &lt;SidebarMenuButton asChild&gt;
          &lt;a href="https://docs.example.com" target="_blank" rel="noopener"&gt;
            &lt;BookOpen className="h-4 w-4" /&gt;
            &lt;span&gt;Documentation&lt;/span&gt;
            &lt;ExternalLink className="ml-auto h-3 w-3" /&gt;
          &lt;/a&gt;
        &lt;/SidebarMenuButton&gt;
      &lt;/SidebarMenuItem&gt;
      &lt;SidebarMenuItem&gt;
        &lt;SidebarMenuButton asChild&gt;
          &lt;a href="mailto:[email protected]"&gt;
            &lt;HelpCircle className="h-4 w-4" /&gt;
            &lt;span&gt;Nous contacter&lt;/span&gt;
          &lt;/a&gt;
        &lt;/SidebarMenuButton&gt;
      &lt;/SidebarMenuItem&gt;
    &lt;/SidebarMenu&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

4.3 Recherche et raccourcis

Beaucoup de back-offices placent une recherche dans la sidebar, ou une recherche globale (Cmd+K). Le composant Command de shadcn/ui convient :

import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command";

&lt;SidebarGroup&gt;
  &lt;SidebarGroupContent&gt;
    &lt;Command className="rounded-lg border shadow-md"&gt;
      &lt;CommandInput placeholder="Rechercher dans le menu..." /&gt;
      &lt;CommandList&gt;
        &lt;CommandEmpty&gt;Aucun résultat&lt;/CommandEmpty&gt;
        &lt;CommandGroup heading="Suggestions"&gt;
          {navConfig.map((item) =&gt; (
            &lt;CommandItem key={item.href} onSelect={() =&gt; router.push(item.href)}&gt;
              &lt;item.icon className="mr-2 h-4 w-4" /&gt;
              {item.title}
            &lt;/CommandItem&gt;
          ))}
        &lt;/CommandGroup&gt;
      &lt;/CommandList&gt;
    &lt;/Command&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

5. Performance et bonnes pratiques

Quelques optimisations en conditions réelles.

Server Components en priorité

Next.js App Router : par défaut tout est Server Component. Les parties statiques de la Sidebar (logo, entrées fixes) peuvent rester en Server Component ; seules les parties interactives (surlignage, repli) passent en "use client".

Mon approche :

  • AppSidebar en "use client" (usePathname)
  • Parties statiques de SidebarHeader, SidebarFooter extraites en Server Component
  • Config de navigation générée côté serveur, passée au composant client

Moins de JS côté client.

Chargement paresseux des grands menus

Des dizaines d’entrées ? Lazy loading avec React.lazy ou dynamic de Next.js :

import dynamic from "next/dynamic";

const AdminMenu = dynamic(() =&gt; import("./admin-menu"), {
  loading: () =&gt; &lt;SidebarMenuSkeleton /&gt;,
});

En pratique, la plupart des back-offices ont peu d’entrées — cas d’usage rare.

Points d’accessibilité

La Sidebar shadcn/ui repose sur Radix UI — l’accessibilité est largement intégrée. Quelques rappels :

  1. Icône + texte : pas d’icône seule — invisible pour les lecteurs d’écran
  2. Focus visible : ne masquez pas le style focus par défaut
  3. Navigation clavier : Tab et flèches doivent fonctionner

Radix gère l’essentiel ; si vous personnalisez, testez la navigation clavier.


6. Questions fréquentes

Q1 : l’état de la Sidebar se perd au refresh ?

Si SidebarProvider est dans le Dashboard Layout au lieu du Root Layout, l’état se réinitialise à chaque navigation. Remontez le Provider dans le Root Layout.

Q2 : fermer automatiquement la Sidebar sur mobile ?

Sur mobile, la Sidebar devient un Sheet. Fermez manuellement après clic sur une entrée :

const { setOpenMobile } = useSidebar();

&lt;SidebarMenuButton
  onClick={() =&gt; setOpenMobile(false)}
&gt;

Q3 : personnaliser la largeur de la Sidebar ?

Variables CSS :

&lt;Sidebar
  style={{
    "--sidebar-width": "280px",
    "--sidebar-width-mobile": "100%",
  }}
&gt;

Ou modifiez la constante SIDEBAR_WIDTH dans sidebar.tsx.


Résumé

shadcn/ui Sidebar avec Next.js Layout — un squelette admin efficace. Points clés :

  1. Écosystème de composants : rôles de SidebarProvider, Sidebar, SidebarContent, etc.
  2. Intégration Layout : Route Groups pour isoler les layouts, SidebarProvider dans le Root Layout
  3. Piloté par config : menu dans un fichier de config, rendu par le composant — maintenance simple
  4. Surlignage de route : usePathname() + prop isActive, direct
  5. Contrôle d’accès : champ roles dans la config, filtrage au rendu

J’utilise cette architecture sur plusieurs projets — bonne extensibilité. Nouvelle page = une entrée dans navConfig, le composant fait le reste.

Des questions ? Commentaires bienvenus. Prochain article : shadcn/ui DataTable en pratique — à suivre si ça vous intéresse.


Références

Construire un squelette admin shadcn/ui Sidebar + Next.js Layout

Partir de zéro pour un layout admin extensible avec sidebar, surbrillance de route et contrôle d'accès

⏱️ Estimated time: 45 min

  1. 1

    Step 1: Installer shadcn/ui et ajouter le composant Sidebar

    Initialiser le projet et ajouter le composant via la CLI :

    ```bash
    npx shadcn@latest init
    npx shadcn@latest add sidebar
    ```

    L'installation demande la configuration des styles — la valeur par défaut suffit. Le fichier sidebar.tsx apparaît ensuite dans components/ui.
  2. 2

    Step 2: Configurer le Root Layout

    Envelopper avec SidebarProvider dans app/layout.tsx :

    • Importer SidebarProvider
    • Envelopper {children} dans la balise body
    • Définir lang="fr-FR"

    Ainsi l'état de la Sidebar reste persistant globalement.
  3. 3

    Step 3: Créer le Dashboard Layout

    Créer app/(dashboard)/layout.tsx pour le layout back-office :

    • Utiliser la syntaxe Route Groups (dashboard)
    • Importer AppSidebar et SidebarInset
    • Ajouter un Header et un fil d'Ariane en haut

    Les Route Groups n'apparaissent pas dans l'URL — /dashboard mappe directement sur la racine.
  4. 4

    Step 4: Définir la configuration de navigation

    Créer lib/navigation.ts :

    • Définir l'interface NavItem (title, href, icon, badge)
    • Exporter le tableau navConfig
    • Optionnel : champ roles pour le contrôle d'accès

    Piloté par config : un seul endroit à modifier pour un nouveau menu.
  5. 5

    Step 5: Implémenter le composant AppSidebar

    Créer components/app-sidebar.tsx :

    • Marquer avec "use client" comme composant client
    • Obtenir la route courante via usePathname
    • Parcourir navConfig pour rendre les entrées
    • Définir isActive pour surligner la route correspondante
  6. 6

    Step 6: Ajouter le contrôle d'accès (optionnel)

    Implémenter le filtrage RBAC :

    • Ajouter le champ roles à l'interface NavItem
    • Récupérer le rôle utilisateur via useAuth dans AppSidebar
    • Filtrer les entrées avec filter

    Sans champ roles, l'entrée est visible pour tous.

FAQ

Quelle différence entre shadcn/ui Sidebar et la sidebar Ant Design ?
shadcn/ui suit le modèle Copy-paste : le code du composant est dans votre projet, entièrement contrôlable. Ant Design est un design system complet — prêt à l'emploi mais coûteux à personnaliser. Pour une personnalisation poussée, choisissez shadcn/ui ; pour aller vite, Ant Design.
SidebarProvider dans le Root Layout ou le Dashboard Layout ?
Recommandation : Root Layout (app/layout.tsx). L'état replié de la Sidebar persiste entre les pages. Dans le Dashboard Layout, l'état se réinitialise à chaque changement de route.
Comment fermer automatiquement la Sidebar sur mobile ?
Sur mobile, la Sidebar devient un tiroir Sheet. Au clic sur une entrée, appelez :

```tsx
const { setOpenMobile } = useSidebar();
&lt;SidebarMenuButton onClick={() =&gt; setOpenMobile(false)}&gt;
```

Le tiroir se referme après le clic.
Comment personnaliser la largeur de la Sidebar ?
Deux approches :

1. Variables CSS (recommandé) :
```tsx
&lt;Sidebar style={{ "--sidebar-width": "280px" }} /&gt;
```

2. Modifier la constante SIDEBAR_WIDTH dans sidebar.tsx

Les variables CSS permettent des largeurs différentes par Sidebar.
Comment implémenter un menu multi-niveaux ?
Envelopper SidebarMenuItem avec Collapsible :

• CollapsibleTrigger pour le bouton de premier niveau
• CollapsibleContent avec SidebarMenuSub et sous-entrées
• Icône ChevronDown pour l'état déplié

Code complet en section 3.5.
Quelle accessibilité pour shadcn/ui Sidebar ?
Construit sur Radix UI — navigation clavier (Tab/flèches), attributs ARIA et gestion du focus intégrés. Associez toujours icône et texte, pas d'icône seule.

13 min de lecture · Publié le: 27 mars 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog