Side Panel
Painel lateral para inspeção, edição contextual e navegação sem abandonar a página atual.
Em cada stack
- HTML e Astro
- Classes
itps-*do CSS Sinapse, com todas as variantes desta página. - React e Vue
- Componentes padrão do shadcn (
sheet,tabs) com o tema Sinapse: cores, fonte e raio vêm do tema, e as variantesitps-*desta página existem só no HTML. O Button é o Sinapse.
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
export function PatientSheet() {
return (
<Sheet>
<SheetTrigger asChild>
<Button>Ver paciente</Button>
</SheetTrigger>
<SheetContent side="right" className="sm:max-w-xl">
<SheetHeader>
<SheetTitle>Maria Silva</SheetTitle>
<SheetDescription>Atendimento iniciado há 12 minutos</SheetDescription>
</SheetHeader>
<div className="py-4">
<p>Conteúdo livre do painel.</p>
</div>
</SheetContent>
</Sheet>
);
} <script setup lang="ts">
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
</script>
<template>
<Sheet>
<SheetTrigger as-child>
<Button>Ver paciente</Button>
</SheetTrigger>
<SheetContent side="right" class="sm:max-w-xl">
<SheetHeader>
<SheetTitle>Maria Silva</SheetTitle>
<SheetDescription>Atendimento iniciado há 12 minutos</SheetDescription>
</SheetHeader>
<div class="py-4">
<p>Conteúdo livre do painel.</p>
</div>
</SheetContent>
</Sheet>
</template> <button type="button" class="itps-btn itps-btn--primary" data-sidepanel-open="patient-panel">
Ver paciente
</button>
<div id="patient-panel" class="itps-side-panel" data-sidepanel aria-hidden="true">
<div class="itps-side-panel__backdrop" data-sidepanel-backdrop></div>
<aside role="dialog" aria-modal="true" aria-labelledby="patient-panel-title" aria-describedby="patient-panel-description" tabindex="-1" class="itps-side-panel__surface" data-sidepanel-content>
<header class="itps-side-panel__header">
<div class="itps-side-panel__header-main">
<div class="itps-side-panel__copy">
<h2 id="patient-panel-title" class="itps-side-panel__title">Maria Silva</h2>
<p id="patient-panel-description" class="itps-side-panel__description">Atendimento iniciado há 12 minutos</p>
</div>
<div class="itps-side-panel__actions">
<button type="button" class="itps-side-panel__close" aria-label="Fechar painel" data-sidepanel-close>
<i class="ti ti-x" aria-hidden="true"></i>
</button>
</div>
</div>
</header>
<div class="itps-side-panel__body" data-sidepanel-body>
<p>Conteúdo livre do painel.</p>
</div>
</aside>
</div> Instalação
O comando instala o Sheet e o Tabs da sua stack e o Button Sinapse com o tema Sinapse. Configure o registry uma vez por projeto em Comece por aqui.
terminal · React/Next
pnpm dlx shadcn@latest add @sinapse/side-panel terminal · Vue 3/Nuxt 4
pnpm dlx shadcn-vue@latest add https://dsystem.sinapse.org.br/r/vue/side-panel.json Sem shadcn, não há instalação por componente: use o CSS Sinapse e as classes da API copy-first desta página.
Uso
Use Side Panel quando a pessoa precisa consultar ou editar um contexto sem sair da página atual. Para uma decisão bloqueante, use Modal.
Em React/Next com shadcn/ui e Vue 3/Nuxt 4 com shadcn-vue, use Sheet com side="right". Para HTML ou wrappers finos, use itps-side-panel com os hooks data-sidepanel*.
Exemplos
Com metadados, ações e tabs
Use metadados no cabeçalho quando a informação ajuda a confirmar contexto. Em tabs, conecte cada itps-side-panel__tab a um role="tabpanel" real por aria-controls.
import { IconEdit, IconShare } from "@tabler/icons-react";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
export function RecordSheet() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Prontuário</Button>
</SheetTrigger>
<SheetContent side="right" className="sm:max-w-xl">
<SheetHeader>
<div className="flex items-start justify-between gap-3">
<div>
<SheetTitle>Ana Costa</SheetTitle>
<SheetDescription>Dados do atendimento atual</SheetDescription>
<p className="mt-2 text-sm text-muted-foreground">Próxima consulta · 12 mai, 14:30</p>
</div>
<div className="flex items-center gap-1">
<Button type="button" variant="ghost" size="icon" aria-label="Editar paciente">
<IconEdit className="size-4" aria-hidden="true" />
</Button>
<Button type="button" variant="ghost" size="icon" aria-label="Compartilhar prontuário">
<IconShare className="size-4" aria-hidden="true" />
</Button>
</div>
</div>
</SheetHeader>
<Tabs defaultValue="summary" className="mt-4">
<TabsList>
<TabsTrigger value="summary">Resumo</TabsTrigger>
<TabsTrigger value="files">Arquivos</TabsTrigger>
</TabsList>
<TabsContent value="summary">Resumo executivo do atendimento.</TabsContent>
<TabsContent value="files">Lista de documentos vinculados ao paciente.</TabsContent>
</Tabs>
</SheetContent>
</Sheet>
);
} <script setup lang="ts">
import { IconEdit, IconShare } from "@tabler/icons-vue";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
</script>
<template>
<Sheet>
<SheetTrigger as-child>
<Button variant="secondary">Prontuário</Button>
</SheetTrigger>
<SheetContent side="right" class="sm:max-w-xl">
<SheetHeader>
<div class="flex items-start justify-between gap-3">
<div>
<SheetTitle>Ana Costa</SheetTitle>
<SheetDescription>Dados do atendimento atual</SheetDescription>
<p class="mt-2 text-sm text-muted-foreground">Próxima consulta · 12 mai, 14:30</p>
</div>
<div class="flex items-center gap-1">
<Button type="button" variant="ghost" size="icon" aria-label="Editar paciente">
<IconEdit class="size-4" aria-hidden="true" />
</Button>
<Button type="button" variant="ghost" size="icon" aria-label="Compartilhar prontuário">
<IconShare class="size-4" aria-hidden="true" />
</Button>
</div>
</div>
</SheetHeader>
<Tabs default-value="summary" class="mt-4">
<TabsList>
<TabsTrigger value="summary">Resumo</TabsTrigger>
<TabsTrigger value="files">Arquivos</TabsTrigger>
</TabsList>
<TabsContent value="summary">Resumo executivo do atendimento.</TabsContent>
<TabsContent value="files">Lista de documentos vinculados ao paciente.</TabsContent>
</Tabs>
</SheetContent>
</Sheet>
</template> <button type="button" class="itps-btn itps-btn--secondary" data-sidepanel-open="record-panel">
Prontuário
</button>
<div id="record-panel" class="itps-side-panel" data-sidepanel aria-hidden="true">
<div class="itps-side-panel__backdrop" data-sidepanel-backdrop></div>
<aside role="dialog" aria-modal="true" aria-labelledby="record-panel-title" aria-describedby="record-panel-description" tabindex="-1" class="itps-side-panel__surface" data-sidepanel-content>
<header class="itps-side-panel__header">
<div class="itps-side-panel__header-main">
<div class="itps-side-panel__copy">
<h2 id="record-panel-title" class="itps-side-panel__title">Ana Costa</h2>
<p id="record-panel-description" class="itps-side-panel__description">Dados do atendimento atual</p>
<div class="itps-side-panel__meta">
<span class="itps-side-panel__meta-item">
<i class="ti ti-calendar" aria-hidden="true"></i>
<span>Próxima consulta</span>
</span>
<span class="itps-side-panel__meta-separator" aria-hidden="true"></span>
<span class="itps-side-panel__meta-item">12 mai, 14:30</span>
</div>
</div>
<div class="itps-side-panel__actions">
<button type="button" class="itps-side-panel__action" aria-label="Editar paciente" title="Editar">
<i class="ti ti-edit" aria-hidden="true"></i>
</button>
<button type="button" class="itps-side-panel__action" aria-label="Compartilhar prontuário" title="Compartilhar">
<i class="ti ti-share" aria-hidden="true"></i>
</button>
<button type="button" class="itps-side-panel__close" aria-label="Fechar painel" data-sidepanel-close>
<i class="ti ti-x" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="itps-side-panel__tabs" role="tablist" aria-label="Seções do painel">
<button type="button" id="record-panel-summary-tab" role="tab" aria-controls="record-panel-summary-panel" aria-selected="true" tabindex="0" class="itps-side-panel__tab" data-sidepanel-tab="summary">
<i class="ti ti-layout-dashboard" aria-hidden="true"></i>
<span>Resumo</span>
</button>
<button type="button" id="record-panel-files-tab" role="tab" aria-controls="record-panel-files-panel" aria-selected="false" tabindex="-1" class="itps-side-panel__tab" data-sidepanel-tab="files">
<i class="ti ti-folder" aria-hidden="true"></i>
<span>Arquivos</span>
</button>
</div>
</header>
<div class="itps-side-panel__body" data-sidepanel-body>
<section id="record-panel-summary-panel" role="tabpanel" aria-labelledby="record-panel-summary-tab" tabindex="0">
<p>Resumo executivo do atendimento.</p>
</section>
<section id="record-panel-files-panel" role="tabpanel" aria-labelledby="record-panel-files-tab" tabindex="0" hidden>
<p>Lista de documentos vinculados ao paciente.</p>
</section>
</div>
</aside>
</div> Estrutura
| Peça | React/Next e Vue 3/Nuxt 4 | HTML |
|---|---|---|
| Raiz | Sheet | itps-side-panel com data-sidepanel |
| Gatilho | SheetTrigger | botão com data-sidepanel-open |
| Superfície | SheetContent side="right" | itps-side-panel__surface |
| Cabeçalho | SheetHeader, SheetTitle, SheetDescription | itps-side-panel__header, __title, __description |
| Tabs | Tabs dentro do conteúdo | itps-side-panel__tabs e itps-side-panel__tab |
Comportamento
A aplicação controla data-state="open", alterna aria-hidden, trava o scroll da página enquanto aberto e devolve foco ao trigger ao fechar.
API copy-first
| Classe/atributo | Uso |
|---|---|
itps-side-panel | Contêiner raiz controlado por aria-hidden e data-state. |
itps-side-panel__backdrop | Camada que fecha o painel ao clicar. |
itps-side-panel__surface | Superfície lateral com role="dialog". |
itps-side-panel__header | Cabeçalho fixo com título, descrição, metadados e ações. |
itps-side-panel__title, itps-side-panel__description | Nome e contexto do painel, referenciados por aria-labelledby e aria-describedby. |
itps-side-panel__meta | Metadados curtos no cabeçalho. |
itps-side-panel__action | Ação curta de cabeçalho com aria-label. |
itps-side-panel__close | Botão de fechar com aria-label="Fechar painel". |
itps-side-panel__tabs, itps-side-panel__tab | Lista de tabs do painel. |
itps-side-panel__body | Área rolável do conteúdo. |
data-sidepanel* | Hooks usados pela aplicação para abrir, fechar, alternar tabs e devolver foco. |
Acessibilidade
- Use
role="dialog"earia-modal="true"na superfície. - Use
aria-labelledbyapontando paraitps-side-panel__title. - Use
aria-describedbyquando houver descrição. - O botão de fechar precisa de
aria-label="Fechar painel". - Em tabs, conecte cada tab a um
role="tabpanel"real comaria-controls. - Feche com Esc, backdrop e botão explícito; devolva foco ao trigger.