Saltar al contenido
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Primeros pasosWebReactSwiftUIJetpack ComposeFlutterReact NativePreajustesTu propio sistema de diseñoUsar Tanrei con agentes de IAMarcaTokens de diseñoSistemas de diseño y personalizaciónTheme StudioColorTipografíaIconosFormaElevaciónMovimientoEstados de interacciónComportamientoDiseño de páginaAccesibilidadTextosTokens de colorTokens de la escala tipográficaTokens de formaTokens de elevaciónTokens de movimientoTokens de estadoTokens de espacio y de medidaComponentesBotónBotón de acción principalBotón segmentadoGrupo de botonesBotón con menúBarra de herramientasBotón de copiarCasilla de verificaciónBotón de opciónInterruptorChipMenúControl deslizanteCampo de control deslizanteCalendarioDateTimePickerGrupo de camposGrupo de botones de opciónGrupo de casillas de verificaciónSelectCampo de selecciónBarra de menúsSubida de archivosSelector de archivos AvanzadoZona de soltar archivosEntrada de texto AvanzadoCampo de textoCampo de chipsCampoCuadro combinado AvanzadoCampo de cuadro combinadoEntrada numérica AvanzadoCampo numéricoEntrada de color AvanzadoCampo de colorEntrada de código de un solo uso AvanzadoCampo de código de un solo usoEntrada de texto incrustada AvanzadoCuadro de redacción de chatTarjetaDivisorCapa modal AvanzadoDiálogoListaFila de listaHoja inferiorHoja lateralCarruselBloque de códigoTablaPopoverPasosAcordeónLista de descripcionesFiguraEstructura de la aplicaciónCabecera de páginaVista de árbolLínea de tiempoVista previa de imágenesTirador de tamañoMarco redimensionableÁrea de desplazamientoInsigniaIndicador de progresoMedidorContador animadoIndicador de cargaSnackbarTooltipAtajo de tecladoTagCódigo en líneaCalloutAvatarEstado vacíoGrupo de tagsEsqueletoElemento de archivoMuestraTexto en marquesinaPestañasVista de pestañasBarra de la aplicaciónBarra de navegaciónPanel de navegaciónBúsquedaEnlacePaginaciónEnlaces anterior y siguienteIndicador de pasosRuta de navegaciónTabla de contenidoTarjeta de enlaceMenú FABEstadoCómo se construye TanreiCómo contribuirLicencia y atribución
InicioComponentesHoja lateral

Hoja lateral

Contiene contenido complementario o una tarea secundaria en un panel situado en el borde de fin, con un titular, un botón de cierre y acciones opcionales, junto al contenido en una ventana ancha o sobre él en una compacta.

content_copyVer en Markdown
Categoría
Contención
Tokens
comp.side-sheet
Nivel
Compuesto
Se compone de
Modal layer

Descripción general

Web
tanrei-side-sheet
schedule
SwiftUI
TanreiSideSheet
schedule
Jetpack Compose
TanreiSideSheet
schedule
Flutter
TanreiSideSheet
schedule
React Native
SideSheet
schedule

Generado a partir de docs/status/status.json, specs/side-sheet.spec.json

Una especificación revisada que aún no está en todas las plataformas

Esta página describe las acciones y el botón de cierre de la hoja lateral tal como los define ahora la especificación, igual que los del diálogo. Las acciones son una fila a todo el ancho de botones de variante tonal, colocados según su función: cada una se marca como confirming o dismissive, y una hoja modal debe ofrecer al menos una. El botón de cierre es un botón small tonal, que se dibuja salvo que closable sea false. Bajo la cabecera hay siempre un divisor, como sobre las acciones, y las acciones se mantienen por encima del teclado en pantalla. El componente web ya hace todo esto, y las demostraciones también; arriba sigue marcado como rezagado hasta que la revisión se haya examinado. SwiftUI, Compose, Flutter y React Native también van rezagados: hasta que se pongan al día ofrecen la revisión anterior, con un botón de icono estándar para cerrar y las acciones en una fila desde el borde de inicio en el orden dado, opcionales en ambas formas y separadas por 8.

Una hoja estándar al final de una fila

Resultados de búsqueda

Filtros

Muestra los resultados de los últimos 30 días que tienen todas las etiquetas seleccionadas.

RestablecerAplicar

Uso

Una hoja lateral es controlada y nunca se cierra sola. La muestras con open. Cuando la persona pide que se vaya, la hoja emite close con el motivo, y tú vuelves a poner open en false. En SwiftUI, donde open es un binding, la hoja escribe false en él y después llama a onClose. Si dejas open en true después de un arrastre, la hoja vuelve a su sitio.

close lleva uno de cinco motivos, con las mayúsculas y minúsculas propias de la plataforma:

Motivo Qué hizo la persona En qué forma
close Activó el botón de cierre Estándar y modal
scrim Pulsó la zona atenuada que hay junto a una hoja modal Modal
escape Pulsó Escape Modal
back Usó el retroceso de la plataforma: el botón o el gesto de retroceso de Android, el gesto de escape de accesibilidad de iOS Modal
drag Arrastró la hoja hacia su borde más allá de la mitad de su ancho, o la lanzó en esa dirección Modal

Una hoja estándar ignora Escape, el retroceso y los arrastres. Su botón de cierre es la única forma que tiene la persona de cerrarla, a menos que lo haga un control tuyo.

La hoja tiene dos formas en variant, y un mismo producto suele usar las dos: estándar donde la ventana es lo bastante ancha, modal donde no lo es.

  • Estándar, la predeterminada, es una región del diseño, no una superposición. Mientras está abierta ocupa su ancho al final de la fila en la que la colocas, y el contenido que tiene al lado cambia de tamaño. No dibuja ningún velo y nunca retiene el foco.
  • Modal es una superposición modal. Se desliza sobre el contenido desde el borde de fin con un velo, mantiene el foco dentro y lo devuelve al cerrarse.

La hoja dibuja una cabecera con el headline y, mientras closable sea true (el valor predeterminado), un botón de cierre que recibe su nombre de closeLabel. Tu contenido va debajo de la cabecera, y los botones de Tanrei del slot de acciones van a lo largo de la parte inferior, cada uno marcado como confirming (completa la tarea: Aplicar, Guardar) o dismissive (la abandona: Cancelar). La hoja los coloca según esas marcas, así que puedes darlos en cualquier orden, y dibuja como tonal toda acción cuya variant no hayas definido. Una hoja modal debe ofrecer al menos una acción; una hoja estándar puede no tener ninguna. Una acción nunca cierra la hoja por sí sola: ciérrala desde el controlador de la acción.

Marca el elemento que debe recibir el foco al abrirse la hoja con la marca propia de la plataforma: autofocus en la web, .defaultFocus o un @AccessibilityFocusState en SwiftUI, un FocusRequester pasado como initialFocus en Compose, autofocus: true en Flutter e initialFocusRef en React Native. En SwiftUI, .defaultFocus mueve el foco del teclado, y un binding @AccessibilityFocusState pasado como initialFocus mueve VoiceOver. Sin marca, el foco va a la propia hoja, de modo que su titular se lee primero.

Los ejemplos de Web y React muestran la especificación actual. SwiftUI, Compose, Flutter y React Native van rezagados: todavía ofrecen la revisión anterior de la hoja lateral, que no tiene las marcas confirming ni dismissive ni el valor predeterminado tonal, así que sus ejemplos dan a cada acción la variante text y ponen primero la acción principal, y muestran esa revisión hasta que se pongan al día.

<tanrei-button id="filters" variant="outlined">Filtros</tanrei-button>

<!-- Estándar: el último elemento de una fila flex. Es un <aside> que recibe su nombre del titular. -->
<div style="display: flex">
  <main style="flex: 1">…</main>
  <tanrei-side-sheet headline="Filtros" close-label="Cerrar filtros">
    <p>Muestra los resultados de los últimos 30 días que tienen todas las etiquetas seleccionadas.</p>
    <!-- confirming va al final de la fila. Sin variante, las dos son tonal. -->
    <tanrei-button slot="actions">Restablecer</tanrei-button>
    <tanrei-button slot="actions" confirming>Aplicar</tanrei-button>
  </tanrei-side-sheet>
</div>

<script type="module">
  import '@tanrei/web/components/side-sheet';
  const sheet = document.querySelector('tanrei-side-sheet');
  document.querySelector('#filters').addEventListener('click', () => (sheet.open = true));
  // La hoja nunca se cierra sola. detail.reason es close, scrim, escape, back o drag.
  sheet.addEventListener('close', (e) => (sheet.open = false));
  // En una ventana de menos de 840px de ancho, la misma hoja como un <dialog> modal sobre un velo.
  matchMedia('(width < 840px)').addEventListener('change', (e) => (sheet.variant = e.matches ? 'modal' : 'standard'));
</script>
import { Button, SideSheet } from '@tanrei/react';

<SideSheet
  variant={wide ? 'standard' : 'modal'}
  open={open}
  headline="Filtros"
  closeLabel="Cerrar filtros"
  onClose={() => setOpen(false)}
>
  <p>Muestra los resultados de los últimos 30 días que tienen todas las etiquetas seleccionadas.</p>
  <Button slot="actions" onClick={reset}>Restablecer</Button>
  <Button slot="actions" confirming onClick={apply}>Aplicar</Button>
</SideSheet>
import Tanrei

// Estándar: una vista al final de un HStack. Al cerrarse escribe false en el binding y después llama a onClose.
HStack(spacing: 0) {
    ResultsView()
    TanreiSideSheet("Filtros", isPresented: $filtersShown, closable: true, closeLabel: "Cerrar filtros") {
        FilterForm()
    } actions: {
        TanreiButton("Aplicar", variant: .text) { apply() }
        TanreiButton("Restablecer", variant: .text) { reset() }
    }
}

// Modal: un modificador en la vista que cubre, presentado como tanreiDialog.
ResultsView()
    .tanreiSideSheet("Filtros", isPresented: $filtersShown, closable: true, closeLabel: "Cerrar filtros") { … }
import io.github.chnotchy.tanrei.components.TanreiButton
import io.github.chnotchy.tanrei.components.TanreiSideSheet
import io.github.chnotchy.tanrei.tokens.TanreiButtonVariant
import io.github.chnotchy.tanrei.tokens.TanreiSideSheetVariant

// Estándar: el último hijo de un Row. Modal: se dibuja en una ventana Dialog propia.
// Pon visible en false desde onClose, que recibe el motivo.
Row {
    ResultsScreen(Modifier.weight(1f))
    TanreiSideSheet(
        visible = filtersShown,
        onClose = { filtersShown = false },
        headline = "Filtros",
        variant = if (wide) TanreiSideSheetVariant.Standard else TanreiSideSheetVariant.Modal,
        closable = true,
        closeLabel = "Cerrar filtros",
        actions = {
            TanreiButton(text = "Aplicar", onClick = apply, variant = TanreiButtonVariant.Text)
            TanreiButton(text = "Restablecer", onClick = reset, variant = TanreiButtonVariant.Text)
        },
    ) {
        FilterForm()
    }
}
import 'package:tanrei/tanrei.dart';

// Estándar: al final de un Row. Una hoja modal va, en cambio, en un Stack sobre la página.
Row(
  children: <Widget>[
    const Expanded(child: ResultsPage()),
    TanreiSideSheet(
      open: filtersShown,
      onClose: (TanreiSheetCloseReason reason) => setState(() => filtersShown = false),
      headline: 'Filtros',
      showCloseButton: true,
      closeButtonSemanticLabel: 'Cerrar filtros',
      actions: <Widget>[
        TanreiButton(variant: TanreiButtonVariant.text, onPressed: apply, child: const Text('Aplicar')),
        TanreiButton(variant: TanreiButtonVariant.text, onPressed: reset, child: const Text('Restablecer')),
      ],
      child: const FilterForm(),
    ),
  ],
),
import { View } from 'react-native';
import { Button, SideSheet } from '@tanrei/react-native';

// Estándar: al final de una fila. Una hoja modal se presenta en un Modal transparente.
<View style={{ flex: 1, flexDirection: 'row' }}>
  <Results style={{ flex: 1 }} />
  <SideSheet
    variant={wide ? 'standard' : 'modal'}
    open={open}
    onClose={(reason) => setOpen(false)}
    headline="Filtros"
    closable
    closeLabel="Cerrar filtros"
    actions={
      <>
        <Button variant="text" onPress={apply}>Aplicar</Button>
        <Button variant="text" onPress={reset}>Restablecer</Button>
      </>
    }
  >
    <FilterForm />
  </SideSheet>
</View>

Especificaciones

Una hoja modal: ábrela con el botón

Filtros

Muestra los resultados de los últimos 30 días que tienen todas las etiquetas seleccionadas.

CancelarAplicar

Anatomía

Anatomía

Dibujado sobre el fixture standard, tal como lo dibuja el paquete web.
Parte Descripción
1 container The panel at the end edge, the full height of its parent (standard) or of the window (modal). Standard: part of the layout. Modal: above the content, the corners that face the content rounded. Its color reaches under the system insets at its end, top and bottom.
2 outline The start edge of a standard sheet, which sets it apart from the content beside it.
3 scrim · opcional The modal layer's scrim (MDL-1): it dims everything behind a modal sheet; a press on it asks the sheet to close.
4 header · opcional The row at the top that holds the headline and the close button. It stays in place while the content scrolls.
5 headline · opcional What the sheet is about ("Filters"). It is the sheet's accessible name.
6 close-button · opcional An icon-only Button of the small size showing the close glyph at the end of the header, in the variant comp.side-sheet.close-button.variant names (tonal in the default system), as every host's close button is. The sheet places it and chooses its variant; everything else it reads from Button's own tokens.
7 content Whatever the product puts in the sheet. The sheet insets it, hands it a content color and scrolls it between the header and the actions; it does not lay it out.
8 divider · opcional Hairlines across the sheet: above the actions, drawn while there are actions; and below the header, drawn whenever the header is in the default system, or only while the content is scrolled away from its top under dividerVisibility scroll.
9 actions · opcional Tanrei Buttons at the bottom of the sheet, each marked confirming or dismissive, laid out as a full-width Button group: a row the actions share equally, or a column when they do not fit. A Button whose variant the product did not set is tonal in the default system. At least one in a modal sheet; optional in a standard one.

Medidas

Medidas

Longitud Token Valor (px)
container width tanrei.comp.side-sheet.container.width 320
container padding-inline tanrei.comp.side-sheet.container.padding-inline 24
Dibujado sobre el fixture standard, tal como lo dibuja el paquete web.

Propiedades

Propiedad Tipo Valor predeterminado Descripción
variant standard | modal "standard" Standard sits beside the content and takes its width in the layout (a layout region); modal slides over the content with a scrim and holds focus until it closes (a modal overlay). One product uses both: standard where the window is wide enough, modal where it is not. A design system may add values.
open boolean false Whether the sheet is shown: for a standard sheet, whether it takes its place in the layout; for a modal one, whether it is presented. The product opens it, and closes it in answer to close; on SwiftUI, where it is a binding, the sheet writes false itself.
headline string "" What the sheet is about, drawn in its header and used as its accessible name. Required.
closable boolean true Draws the close button at the end of the header. True by default, in both variants: a standard sheet has no other way for the person to close it, and a modal one shows a way out where the person reads. Set it to false only where the product's own control, or one of the sheet's actions, closes the sheet.
closeLabel string "" The close button's accessible name ("Close filters"). Required while closable is true, which is the default.
dividerVisibility auto | always | scroll "auto" When the divider below the header is drawn. always: whenever the header is drawn, at rest as well as while the content is scrolled, as the divider above the actions always is. scroll: only while the content is scrolled away from its top. auto takes comp.side-sheet.divider.visibility, the design system's answer (always in the default system).

Slots

Slot Descripción
default The content, below the header. A fixture's content string is plain text, which takes sys.typescale.body in the content color (SSH-10).
actions The action items: Tanrei Buttons, each marked confirming or dismissive; at least one in a modal sheet (SSH-16). The sheet places them (SSH-11) and gives those without a variant its own (SSH-17). A fixture fills it through its items.

Eventos

Evento Descripción
close The sheet asks to be closed: its close button, or for a modal sheet a press on the scrim, Escape, the platform's back or a drag toward its edge. Carries which of these it was, named close, scrim, escape, back or drag (in the platform's casing).

Elementos

Los tipos de elemento que el componente repite. Un fixture enumera sus elementos en orden; cada plataforma nombra cada tipo según su propio estilo.

action

One action at the foot of the sheet: a Tanrei Button, placed by the sheet as a full-width Button group places its items and given the sheet's variant unless the product set one.

Propiedad Tipo Valor predeterminado Descripción
variant string "" The Button's variant (filled, tonal, elevated, outlined, text, or one a system added). Empty, the default: the sheet gives it the variant comp.side-sheet.actions.variant names, tonal in the default system (SSH-17). Set it to give one action another look.
disabled boolean false As the Button's disabled. A disabled action keeps its place and its share of the width.
confirming boolean false The action completes the task (Apply, Save, Show results). Placed at the end of a row and at the top of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19).
dismissive boolean false The action backs out of the task (Cancel, Discard changes). Placed at the start of a row and at the bottom of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19).
Slot Descripción
label The Button's label, as its default slot. A fixture item's content is this label.
Evento Descripción
click The action was activated, as the Button's click. The sheet does not close by itself (SSH-18).
Especificación Web SwiftUI Jetpack Compose Flutter React Native
Elemento tanrei-button TanreiButton TanreiButton TanreiButton Button
variant · Propiedad variant variant variant variant variant
disabled · Propiedad disabled disabled enabled onPressed disabled
confirming · Propiedad confirming tanreiGroupPlacement(.confirming) Modifier.confirming() TanreiButtonGroupItem.placement confirming
dismissive · Propiedad dismissive tanreiGroupPlacement(.dismissive) Modifier.dismissive() TanreiButtonGroupItem.placement dismissive
click · Evento click action onClick onPressed onPress
Notas A tanrei-button child with slot="actions": its label is its content. confirming and dismissive are boolean attributes that the sheet reads, as the button group does. A Button without a variant attribute takes the sheet's variant through the internal controller the group already gives its items (SSH-17); the attribute, once written, wins. A TanreiButton in the actions: builder. The flags are the button group's .tanreiGroupPlacement(_:) modifier (one enum: both cannot be set). A Button created with variant: nil reads the sheet's variant from an environment value the sheet sets (internal to the package). A TanreiButton in the actions lambda, whose receiver is the button group's scope, so the flags are its Modifier.confirming() and Modifier.dismissive(). enabled is disabled's inverse. variant: TanreiButtonVariant? = null takes the sheet's from a CompositionLocal (internal to the library). A TanreiButton in actions:; a null onPressed disables it. The flags are the button group's TanreiButtonGroupItem(placement:, child:) (one enum: both cannot be set). A Button with variant: null takes the sheet's from an InheritedWidget (internal to the package). A Button in the actions prop. confirming and dismissive are props the sheet reads from its actions, as the button group reads them. A Button with no variant takes the sheet's from a React context (internal to the package).

Estados

Comportamiento

Requisitos en formato EARS. Cada implementación de plataforma se prueba contra estos identificadores. El texto de los requisitos se muestra tal como está en la especificación, en inglés.

ID Requisito
SSH-1 WHILE variant is standard THE side sheet SHALL take its width in the layout at the end of the row the product places it in WHILE open is true, and no width and no presence to assistive technology or focus WHILE open is false; WHEN open changes it SHALL slide in or out at the end edge while the content beside it resizes, over comp.side-sheet.motion.enter-duration with comp.side-sheet.motion.enter-easing when opening and comp.side-sheet.motion.exit-duration with comp.side-sheet.motion.exit-easing when closing, and at once where motion is reduced. A standard sheet SHALL draw no scrim, SHALL NOT keep focus inside it, SHALL let Tab and Shift+Tab move focus out of it without closing it, and SHALL ignore Escape, the platform's back and drags (a standard sheet is a layout region, not an overlay).
SSH-2 WHEN open becomes true WHILE variant is modal THE side sheet SHALL slide in from the window's end edge over the content on its modal layer, fading the layer's scrim in comp.side-sheet.scrim.color and comp.side-sheet.scrim.blur in over the rest of the window in step with it (MDL-1, MDL-7), over comp.side-sheet.motion.enter-duration with comp.side-sheet.motion.enter-easing; WHEN open becomes false it SHALL slide out to the end edge from wherever it is and fade the scrim out, over comp.side-sheet.motion.exit-duration with comp.side-sheet.motion.exit-easing. WHERE motion is reduced it SHALL fade in and out without sliding, or appear and disappear at once on a platform whose reduced-motion setting removes every animation (Android's). WHILE it is open THE side sheet SHALL make everything behind it inert and keep focus inside it, as its modal layer does (MDL-2, MDL-4).
SSH-3 WHEN open changes from false to true THE side sheet SHALL move focus to the element in it that the product marked for initial focus (the platform's own mark, named in the platform notes) or, without one, to its container, so that assistive technology reads the headline first; WHEN it closes while focus is inside it THE side sheet SHALL return focus to the element that had it when the sheet opened or, where that element no longer exists or cannot take focus, to the platform's default. Focus SHALL move when the sheet opens or closes, not when its motion ends. This holds for both variants (a modal sheet moves and returns focus as its modal layer does, MDL-3, MDL-5); a standard sheet mounted with open true SHALL NOT move focus, and a modal sheet mounted with open true SHALL move focus in as if it had just opened, because it makes everything behind it unfocusable and focus must be somewhere it can reach.
SSH-4 WHILE variant is modal and the sheet is open, WHEN the user presses the scrim, presses Escape, uses the platform's back, as its modal layer answers them (MDL-6), or drags the sheet toward its end edge past half its width or flings it that way, THE side sheet SHALL emit close exactly once with the reason scrim, escape, back or drag; a drag SHALL follow the finger or pointer once it passes the platform's touch slop, and a released drag that does not ask to close SHALL slide the sheet back over comp.side-sheet.motion.enter-duration with comp.side-sheet.motion.enter-easing (at once where motion is reduced). The fling, the touch slop and the stillness after which a release is no fling are those BSH-9 names for the bottom sheet: the platform's own, or 0.5 points per millisecond, 8 points and 40 milliseconds where the platform names none.
SSH-5 WHILE closable is true THE side sheet SHALL draw at the end of its header an icon-only Button of the small size showing the close glyph and named by closeLabel, in the variant comp.side-sheet.close-button.variant names, given through the channel a Button reads from the component that places it, and otherwise unrestyled: it reads Button's own tokens, answers hover, focus and press as any Button does and keeps its own minimum target (BTN-5); WHEN the user activates it THE side sheet SHALL emit close exactly once with the reason close, in either variant. WHILE closable is true and closeLabel is empty it SHALL still draw the button, and in debug builds report that the button has no accessible name.
SSH-6 WHEN the side sheet has emitted close THE side sheet SHALL be closed through its binding where open is one (SwiftUI), and by the product setting open to false where open is a plain value (web, Compose, Flutter, React Native); a released drag that asked to close SHALL hold where it left the sheet for one frame, then slide out from there IF open has become false, or slide back IF open is still true (the product declined to close it).
SSH-7 THE side sheet SHALL be comp.side-sheet.container.width wide beside the end system inset — WHILE variant is modal never wider than the window less sys.target.touch, so that a strip of scrim stays pressable — and the full height of its parent (standard) or of the window (modal), filled with its variant's container color (comp.side-sheet.container.color.standard or comp.side-sheet.container.color.modal), raised by its variant's elevation (comp.side-sheet.container.elevation.standard, comp.side-sheet.container.elevation.modal), with the corners at its start edge rounded by its variant's shape (comp.side-sheet.container.shape.standard, comp.side-sheet.container.shape.modal) eased by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and square corners at its end edge, and edged at its start by comp.side-sheet.outline.width of its variant's outline color (comp.side-sheet.outline.color.standard, comp.side-sheet.outline.color.modal); right-to-left layouts SHALL put it at the left edge, mirrored.
SSH-8 THE side sheet SHALL lay out, top to bottom: WHILE headline is not empty or closable is true, the header, a row at least comp.side-sheet.header.height high holding the headline from comp.side-sheet.container.padding-inline after the start edge and, WHILE closable is true, the close button ending comp.side-sheet.header.padding-inline-end before the end edge, comp.side-sheet.header.gap after the headline, both centred in the row; then the content, inset by comp.side-sheet.container.padding-inline from the start and end edges, starting directly below the header or, without one, comp.side-sheet.container.padding-block below the top edge; then, WHILE the sheet has at least one action, the divider and the actions pinned to the bottom (SSH-11), or else comp.side-sheet.container.padding-block of space below the content. It SHALL extend its container under the system insets at its end, top and bottom and place the header, content and actions inside them.
SSH-9 WHILE headline is not empty THE side sheet SHALL draw it in comp.side-sheet.headline.typescale and comp.side-sheet.headline.color, wrapping onto more lines rather than truncating and growing the header with it, and SHALL expose it as a heading. THE side sheet SHALL set the headline's letter-spacing by the script of its text, as every title is set (PRS-20): no negative letter-spacing WHERE its language is Chinese, Japanese or Korean, and none WHERE its script joins its letters or stacks marks on them.
SSH-10 THE side sheet SHALL hand its content comp.side-sheet.content.color as the platform's inherited foreground (the web's inherited color, SwiftUI's foreground style, Compose's content color, Flutter's default text and icon style, React Native's Tanrei text context); plain text given as content, where the platform must style it, SHALL take sys.typescale.body. WHILE the content is taller than the room between the header and the actions THE side sheet SHALL scroll it there, keeping the header and the actions in place, and SHALL let assistive technology and keyboard focus scroll it.
SSH-11 WHILE the sheet has at least one action THE side sheet SHALL draw a divider of comp.side-sheet.divider.width in comp.side-sheet.divider.color across its full width below the content, whether or not the content scrolls, and below it lay out its actions as a standard Button group lays out its items (BGR-12 to BGR-19), comp.side-sheet.actions.gap apart, comp.side-sheet.container.padding-inline from each side edge and comp.side-sheet.actions.padding-block above and below them, in the order the group gives them — dismissive at the start and confirming at the end of a row, confirming on top and dismissive at the bottom of a column, unflagged actions between them in the order given — with focus and reading order following that order (BGR-15). WHILE comp.side-sheet.actions.arrangement is fill it SHALL make them a full-width group across the sheet's inner width, the actions sharing a row equally (BGR-17) or each as wide as the column; WHILE it is start it SHALL keep them at their own widths, the row aligned to the start edge, and a column as wide as its widest action and aligned to the start. It SHALL place them in a row WHILE every action's label fits its share on one line (fill) or the row at its own widths fits the inner width (start), and otherwise in a column, re-deciding when the sheet's width or the text size changes. WHEN the layout direction is right-to-left it SHALL place them as a Button group places them right to left (BGR-8). It SHALL hide the divider from assistive technology.
SSH-12 THE side sheet SHALL expose its container, WHILE variant is modal, as its modal layer's modal dialog (MDL-10) named by headline and, WHILE variant is standard, as a complementary region named by headline (the web's aside landmark); WHILE headline is empty it SHALL still render, and in debug builds report that it has no accessible name.
SSH-13 WHEN variant names a value that the active design system does not have THE side sheet SHALL render with the default value's tokens, and in debug builds report the unknown value.
SSH-14 WHILE the text size is enlarged THE side sheet SHALL keep its width, grow its header with the headline (its height is a minimum), scroll its content (SSH-10), stack its actions once a label no longer fits its share (SSH-11), and SHALL NOT scale the close button beyond what the Button itself does.
SSH-15 WHEN a dialog, a menu or another modal sheet opens from a modal side sheet THE side sheet SHALL stay below it, become inert with the rest of the window, and on the other overlay's closing receive back the focus that overlay returns, as its modal layer does (MDL-8, MDL-9); Escape SHALL dismiss only the topmost overlay.
SSH-16 WHILE variant is modal, IF the sheet opens with no action THE side sheet SHALL still open and lay out its other parts, and in debug builds SHALL report as an error that a modal side sheet must offer at least one action; a platform whose modal sheet has an API of its own that can require an action (a required, non-empty builder or list of actions) SHALL require it there, and one that can count its actions only when the sheet opens SHALL count them then. WHILE variant is standard THE side sheet SHALL accept having no action and report nothing, and SHALL then draw neither the divider above the actions nor the actions (SSH-8).
SSH-17 WHILE an action's variant is empty THE side sheet SHALL give that Button the variant comp.side-sheet.actions.variant names, through the channel a Button reads from the component that places it, so that the Button renders from that variant's tokens and states as it would with the variant set; WHILE an action's variant is set THE side sheet SHALL leave it as set. THE side sheet SHALL NOT change an action's size or tokens, and SHALL NOT change its look or emphasis for its confirming or dismissive (BGR-19).
SSH-18 WHEN the user activates an action THE side sheet SHALL let the Button emit its own click and SHALL NOT close, emit close or otherwise answer it itself, whether the action is confirming, dismissive or neither; the product closes the sheet from the action's handler, as it does in answer to close.
SSH-19 WHILE the header is drawn THE side sheet SHALL draw a divider of comp.side-sheet.divider.width in comp.side-sheet.divider.color across its full width below the header, hidden from assistive technology: WHILE its divider visibility (SSH-23) is always, at rest as well as while the content is scrolled, as the divider above the actions is drawn whether or not the content scrolls (SSH-11); WHILE it is scroll, only WHILE the content is scrolled away from its top. WHILE there is no header it SHALL draw none there. It SHALL show and hide this divider at once, without motion.
SSH-20 WHILE the on-screen keyboard covers the bottom of the sheet THE side sheet SHALL keep its header in place, raise its divider and actions to sit directly above the keyboard, scroll the content in the room left between them, and scroll the focused element into that room; WHEN the keyboard hides it SHALL return the actions to the bottom edge. It SHALL move them as the platform moves content for its keyboard, and at once where motion is reduced.
SSH-21 THE side sheet SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the side sheet is shown THE side sheet SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus.
SSH-22 THE side sheet SHALL lay out its headline as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE side sheet SHALL still place the headline and align its lines by the layout direction, not by the text's own.
SSH-23 THE side sheet SHALL draw the divider below its header (SSH-19) as its dividerVisibility names: WHILE dividerVisibility is auto, as comp.side-sheet.divider.visibility names, which follows sys.behavior.divider-visibility unless a design system sets the side sheet apart; WHILE dividerVisibility is always or scroll, as that value names, whatever the tokens say. WHEN dividerVisibility, or the token it follows, changes THE side sheet SHALL draw the other form at once, moving no part and keeping the scroll position and focus; WHEN dividerVisibility names a value the side sheet does not define THE side sheet SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, always, and state that in its README.
SSH-24 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE side sheet SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on its container (SSH-12) — its dialog WHILE variant is modal, its complementary region otherwise — an id reference by the elements it names in the host's tree or a tree around it, a description after its own. WHILE headline is not empty it is the container's name, over a name given so; WHILE headline is empty, a name given so is the container's name and SHALL NOT be reported as having none in debug builds. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE side sheet SHALL take it off that element.

Correspondencia de nombres por plataforma

Paridad no significa API idénticas. Cada plataforma expresa el mismo contrato con su propio estilo.

Especificación Web SwiftUI Jetpack Compose Flutter React Native
Componente tanrei-side-sheet TanreiSideSheet TanreiSideSheet TanreiSideSheet SideSheet
variant · Propiedad variant variant variant variant variant
open · Propiedad open isPresented visible open open
headline · Propiedad headline headline headline headline headline
closable · Propiedad closable closable closable showCloseButton closable
closeLabel · Propiedad closeLabel closeLabel closeLabel closeButtonSemanticLabel closeLabel
dividerVisibility · Propiedad dividerVisibility dividerVisibility dividerVisibility dividerVisibility dividerVisibility
close · Evento close onClose onClose onClose onClose
Notas closable is a boolean property that defaults to true; its attribute is the inverse not-closable (an HTML boolean attribute cannot default to true), as the chip's removable is. closeLabel is the close-label attribute. close is a CustomEvent whose detail.reason is close, scrim, escape, back or drag; the product sets open to false from it. Slots: default and actions. The actions are laid out by the button group's placement (BGR-13 to BGR-17) inside the shadow root: the sheet's shadow root uses manual slot assignment, assigns its content children to the default slot, and assigns each action to one of three action slots in the order of the orientation, so focus and the accessibility tree follow the placed order (BGR-15); CSS order is not used. Modal: a <dialog> in the shadow root opened with showModal(), its ::backdrop the scrim. Standard: an <aside> named by the headline, placed by the product as the last item of a flex row; its width animates. The close button is a <tanrei-button> in the shadow root, icon-only (the close glyph in its icon slot, its label from close-label), whose variant comes from the resolved comp.side-sheet.close-button.variant through the internal controller the group gives its items. The autofocus attribute on a slotted element marks initial focus. Debug builds (import.meta.env.DEV or the package's dev export) report a modal sheet opened with no assigned action (SSH-16). The keyboard's room (SSH-20) is the visualViewport's height. Insets are env(safe-area-inset-*). Snapshot harness: a 720 × 480 viewport with a surface page; the standard sheet at the end of a row whose other child fills the rest of the width; the modal sheet opened by open, the screenshot being the viewport. dividerVisibility is the divider-visibility attribute (auto, always, scroll); auto reads --tanrei-comp-side-sheet-divider-visibility from the cascade with a style query, and where the browser has none the sheet writes the name it read on its header when it renders. Lagging: ships the earlier revision — a standard icon button to close, the actions a row from the start edge in the order given, optional in both variants, 8 apart. The target: standard TanreiSideSheet(_ headline:, isPresented: Binding<Bool>, closable:, closeLabel:, initialFocus:, onClose:) { content } actions: { … }, a view the product places at the end of an HStack, with actions: optional. Modal: the modifier .tanreiSideSheet(_ headline:, isPresented:, …, content:, actions:) on the view it covers, with actions: a required builder whose subviews the sheet counts through a variadic view, reporting an empty one in debug builds (SSH-16); presented as tanreiDialog is (on iOS .fullScreenCover with a clear presentation background and the system transition off; Tanrei draws the scrim, the sheet and the drag), and on macOS, which has no full-screen cover, as an overlay of the modified view with the modal trait. Both lay out the actions with the button group's Layout. Closing writes false to isPresented and then calls onClose with the reason. The close button is an icon-only TanreiButton whose variant the sheet sets from its token. Initial focus is .defaultFocus, or an @AccessibilityFocusState binding the product passes as initialFocus (without it, VoiceOver starts at the headline); Escape is .onKeyPress(.escape) and the escape gesture .accessibilityAction(.escape). The keyboard's safe area lifts the actions (SSH-20). Snapshot harness: renders TanreiSideSheetSurface (the sheet and, for a modal sheet, the scrim) in place in a 720 × 480 frame over a surface page. Not yet: the headline's letter-spacing by the script of its text (SSH-9, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; SSH-23) — the divider below the header is still drawn only while the content is scrolled from its top, as scroll does. Lagging: ships the earlier revision — a standard icon button to close, the actions a row from the start edge in the order given, optional in both variants, 8 apart. The target: TanreiSideSheet(visible, onClose: (TanreiSheetCloseReason) -> Unit, headline, variant, closable, closeLabel, initialFocus: FocusRequester?, actions: (@Composable () -> Unit)?, content); the caller sets visible to false from onClose. The actions lambda's receiver is the button group's scope, not RowScope, so the flags are its Modifier.confirming() and Modifier.dismissive() and no inert weight or align is offered. Its Layout sees the actions' measurables and, for a modal sheet, reports none in debug builds (SSH-16); variant is a parameter, so the type cannot require actions for the modal form alone. Standard: a composable the product places at the end of a Row; its width animates. Modal: drawn in a Dialog window of its own, as TanreiDialog and the modal drawer are, with the window's own dim off (Tanrei draws the scrim); the window traps focus, answers back and hides the content behind from TalkBack, and the sheet sets a pane title from the headline. The close button is an icon-only TanreiButton whose variant the sheet sets from its token. The keyboard's room is WindowInsets.ime (SSH-20). Snapshot harness: renders TanreiSideSheetContent (the sheet and, for a modal sheet, the scrim) in place in a 720 × 480 dp box over a surface page. Not yet: the headline's letter-spacing by the script of its text (SSH-9, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; SSH-23) — the divider below the header is still drawn only while the content is scrolled from its top, as scroll does. Lagging: ships the earlier revision — a standard icon button to close, the actions a row from the start edge in the order given, optional in both variants, 8 apart. The target: TanreiSideSheet(variant:, open:, onClose: ValueChanged<TanreiSheetCloseReason>, headline:, showCloseButton:, closeButtonSemanticLabel:, actions: [...], child:), controlled: the caller sets open to false from onClose. A modal sheet asserts actions non-empty when it opens (SSH-16); the actions are laid out with the button group's render object. Standard: placed at the end of a Row. Modal: a controlled overlay in a Stack over the page, as the modal navigation drawer, with its own scrim and slide, a FocusScope that keeps focus in, the page's semantics blocked, back through PopScope and Escape through Shortcuts/Actions. The close button is an icon-only TanreiButton whose variant the sheet sets from its token; the headline is wrapped in Semantics(header: true). The keyboard's room is MediaQuery.viewInsetsOf(context).bottom (SSH-20). Snapshot harness: a golden of the whole 720 × 480 surface over a surface page. Not yet: the headline's letter-spacing by the script of its text (SSH-9, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; SSH-23) — the divider below the header is still drawn only while the content is scrolled from its top, as scroll does. Lagging: ships the earlier revision — a standard icon button to close, the actions a row from the start edge in the order given, optional in both variants, 8 apart. The target: controlled: open, onClose(reason); the caller sets open to false from it. actions is a React node (a Button or a fragment of them); development builds report it empty when a modal sheet opens (SSH-16); the types cannot tie it to variant. The content is children. Standard: a View the product places at the end of a row (flexDirection: 'row'). Modal: presented in a transparent Modal with animationType="none", statusBarTranslucent and navigationBarTranslucent; onRequestClose (Android back, and Escape on a hardware keyboard) is close with the reason back or escape. Drags with PanResponder. The close button is an icon-only Button whose variant the sheet sets from its token; the headline has the header role. The keyboard's height comes from Keyboard events (SSH-20). Insets come from the sheet's own insets prop, falling back to TanreiProvider's insets; the package takes no safe-area dependency. Snapshot harness: renders SideSheetSurface in place in a 720 × 480 view over a surface page. The initial-focus mark is an initialFocusRef prop, as on the bottom sheet and the dialog (SSH-3). Not yet: the headline's letter-spacing by the script of its text (SSH-9, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; SSH-23) — the divider below the header is still drawn only while the content is scrolled from its top, as scroll does.

Tokens

Token Alias de Valor resuelto (claro)
--tanrei-comp-side-sheet-actions-arrangement — 0
--tanrei-comp-side-sheet-actions-gap sys.space.space200 16px
--tanrei-comp-side-sheet-actions-padding-block sys.space.space200 16px
--tanrei-comp-side-sheet-actions-variant — 1
--tanrei-comp-side-sheet-close-button-variant — 1
--tanrei-comp-side-sheet-container-color-modal sys.color.surface-container-low #f9f9f9
--tanrei-comp-side-sheet-container-color-standard sys.color.surface #ffffff
--tanrei-comp-side-sheet-container-elevation-modal sys.elevation.level2 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-side-sheet-container-elevation-standard sys.elevation.level0
--tanrei-comp-side-sheet-container-padding-block sys.space.space300 24px
--tanrei-comp-side-sheet-container-padding-inline sys.space.space300 24px
--tanrei-comp-side-sheet-container-shape-modal sys.shape.corner.large 12px
--tanrei-comp-side-sheet-container-shape-standard sys.shape.corner.none 0px
--tanrei-comp-side-sheet-container-width — 320px
--tanrei-comp-side-sheet-content-color sys.color.on-surface #1b1b1b
--tanrei-comp-side-sheet-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-side-sheet-divider-visibility sys.behavior.divider-visibility 0
--tanrei-comp-side-sheet-divider-width sys.border.hairline 1px
--tanrei-comp-side-sheet-header-gap sys.space.space100 8px
--tanrei-comp-side-sheet-header-height — 64px
--tanrei-comp-side-sheet-header-padding-inline-end sys.space.space150 12px
--tanrei-comp-side-sheet-headline-color sys.color.on-surface #1b1b1b
--tanrei-comp-side-sheet-motion-enter-duration sys.motion.role.appear.sheet.enter 400ms
--tanrei-comp-side-sheet-motion-enter-easing sys.motion.easing.enter cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-side-sheet-motion-exit-duration sys.motion.role.appear.sheet.exit 350ms
--tanrei-comp-side-sheet-motion-exit-easing sys.motion.easing.exit cubic-bezier(0.3, 0, 1, 1)
--tanrei-comp-side-sheet-outline-color-modal — #000000 · 0%
--tanrei-comp-side-sheet-outline-color-standard sys.color.outline-variant #000000 · 8%
--tanrei-comp-side-sheet-outline-width sys.border.hairline 1px
--tanrei-comp-side-sheet-scrim-blur comp.modal-layer.scrim.blur 0px
--tanrei-comp-side-sheet-scrim-color comp.modal-layer.scrim.color #000000 · 40%
--tanrei-comp-side-sheet-headline-typescale sys.typescale.heading-5 500 16px/1.5

Fixtures

Representaciones canónicas. Todas las plataformas capturan cada fixture con el mismo identificador para poder comparar los resultados.

Generado a partir de specs/side-sheet.spec.json, tokens/comp/side-sheet.tokens.json, design/tokens/

Pautas

Cuándo usarlo

Usa una hoja lateral para contenido que apoya aquello en lo que la persona está trabajando sin sustituirlo: los filtros de una lista, los detalles de una fila seleccionada, las propiedades de un objeto seleccionado. En una ventana ancha se sitúa junto al contenido, de modo que la persona puede cambiar un filtro y ver cómo cambian los resultados. En una ventana compacta, la misma hoja se desliza sobre el contenido, y la persona termina con ella o la descarta antes de volver.

En un teléfono, a menudo encaja mejor una hoja inferior: sube desde el borde, al alcance del pulgar. Usa un diálogo para una decisión sin la cual la tarea no puede continuar, y un panel de navegación, en el borde de inicio, para los destinos del producto.

Estándar o modal

Variante Qué es Úsala para
Standard (predeterminada) Una región del diseño: un panel al final de la fila, separado del contenido por una línea fina en su borde de inicio. El contenido que tiene al lado cambia de tamaño cuando se abre y se cierra. No dibuja ningún velo y permanece abierta mientras la persona trabaja en el contenido. Ventanas anchas, donde la hoja puede quedarse abierta junto al contenido.
Modal Una superposición modal: un panel elevado, con redondeadas las esquinas que dan al contenido, que entra deslizándose desde el borde de fin sobre un velo. Todo lo que queda detrás está inerte, y el foco se mantiene dentro hasta que se cierra. Ventanas compactas y tareas que hay que terminar o descartar antes de que la persona continúe.

Una hoja estándar forma parte del diseño, como un panel de navegación estándar o una barra de la aplicación: que el foco salga de ella nunca la cierra, porque cerrar un panel cada vez que Tab siguiera adelante lo haría inutilizable desde el teclado. Su forma modal es una superposición, y se aplica todo lo que promete una superposición modal.

Una hoja modal nunca es tan ancha como una ventana estrecha. Se queda al menos un objetivo táctil más estrecha, de modo que siempre haya una franja de velo que pulsar. Un sistema de diseño puede añadir variantes.

Partes

Parte Se define con Notas
Titular headline Obligatorio. De qué trata la hoja, como «Filtros». Es el nombre accesible de la hoja y un encabezado, y salta de línea en lugar de cortarse.
Botón de cierre closable, closeLabel Un botón small de solo icono con el glifo close al final de la cabecera, tonal en el sistema predeterminado, como todos los botones de cierre de Tanrei (un glifo sin fondo en m3-like). Se dibuja de forma predeterminada; pon closable a false (el atributo not-closable) cuando la hoja la cierre un control tuyo o una acción. La hoja lo dibuja y lo conecta con close; tú le das una etiqueta.
Contenido el slot predeterminado Aquello para lo que sirve la hoja. La hoja le da un margen interior, le asigna un color de contenido y lo desplaza entre la cabecera y las acciones.
Acciones el slot de acciones Botones de Tanrei, cada uno marcado como confirming o dismissive, bajo un divisor. Obligatorias en una hoja modal, opcionales en una estándar. La hoja las coloca y dibuja como tonal las que no tienen variante; por lo demás, nunca les cambia el estilo.

Las acciones se reparten a partes iguales el ancho interior de la hoja, con 16 entre ellas. En una fila, la acción dismissive está al inicio y la confirming al final, con las acciones sin marca entre ambas en el orden dado. Cuando una etiqueta tendría que saltar de línea en su parte, se apilan en una columna con la acción confirming arriba. El foco y los lectores de pantalla siguen el orden de colocación. Define la variant de un botón para dar otro aspecto a esa acción; un producto que quiera otro aspecto para todas las hojas lo define una sola vez en su capa (actions.variant, y actions.arrangement para una fila compacta al inicio, o close-button.variant para el botón de cierre).

La cabecera y las acciones permanecen en su sitio mientras el contenido se desplaza. El divisor de encima de las acciones se dibuja siempre, y en el sistema predeterminado también un divisor igual bajo la cabecera: ambos son bordes de un panel que se queda junto a la página. Define dividerVisibility como scroll (divider-visibility en la web) para dibujar el de la cabecera solo mientras el contenido está desplazado respecto a su parte superior; cupertino y m3-like lo eligen para todas las hojas. Mientras el teclado en pantalla cubre la parte inferior de la hoja, las acciones suben hasta quedar justo encima de él, y el contenido se desplaza en el espacio que queda. El color de la hoja llega por debajo de los márgenes interiores del sistema en su fin, su parte superior y su parte inferior, y su cabecera, su contenido y sus acciones se quedan dentro de ellos. En un diseño de derecha a izquierda, la hoja se sitúa en el borde izquierdo, reflejada.

Apertura, foco y cierre

  • Abrir una hoja estándar. Entra deslizándose por el borde de fin mientras el contenido que tiene al lado le hace sitio. El foco pasa al elemento que marcaste, o a la hoja, de modo que su titular se lee primero. Una hoja que está abierta desde el principio no mueve el foco.
  • Trabajar junto a una hoja estándar. Tab pasa de la hoja al contenido, y la hoja sigue abierta. Escape, el retroceso y los arrastres no le hacen nada.
  • Abrir una hoja modal. Entra deslizándose desde el borde de fin mientras el velo aparece con un fundido sobre el resto de la ventana. El foco entra como en la hoja estándar y se queda dentro: Tab desde el último elemento enfocable va al primero, y Mayús+Tab desde el primero va al último. Todo lo que queda detrás está inerte. Una hoja modal que está abierta desde el principio también lleva el foco dentro, porque nada de lo que hay detrás puede recibirlo.
  • Cerrar. La hoja sale deslizándose hacia el borde de fin y, en una hoja modal, el velo se desvanece. Si el foco estaba dentro, vuelve al elemento que lo tenía cuando se abrió la hoja.
  • Arrastrar una hoja modal. Sigue al dedo en cuanto este supera el umbral de arrastre táctil de la plataforma. Soltarla más allá de la mitad de su ancho hacia el borde de fin, o lanzarla en esa dirección, le pide que se cierre con el motivo drag. Un arrastre más corto y más lento la devuelve a su sitio. El lanzamiento y el umbral son los que usa la hoja inferior.
  • Apilamiento. Un diálogo, un menú u otra hoja modal que se abren desde una hoja lateral modal se apilan por encima de ella. Escape descarta solo la superior.
  • Sin deslizamiento desde el borde para abrir. La hoja se abre desde un botón, nunca con un deslizamiento desde el borde de la pantalla.
  • Donde el movimiento está reducido, una hoja estándar se abre y se cierra de inmediato, y una hoja modal aparece y desaparece con un fundido, sin deslizarse.

Recomendado

  • Escribe un titular corto que nombre el contenido de la hoja: «Filtros», «Detalles», «Propiedades».
  • Conserva el botón de cierre de una hoja estándar, que se dibuja de forma predeterminada, a menos que la cierre un control tuyo, como un botón Filtros conmutado.
  • Usa el mismo contenido en las dos formas, para que el producto cambie de una a otra solo por el ancho de la ventana.
  • Marca cada acción como confirming o dismissive, y deja que la hoja las coloque.
  • Da a una hoja modal una acción que termine la tarea, como Aplicar, y no solo formas de salir.

Evita

  • No uses una hoja lateral modal en una ventana lo bastante ancha para una estándar.
  • No pongas la navegación del producto en una hoja lateral. Usa un panel de navegación.
  • No cambies el estilo del botón de cierre ni de los botones del slot de acciones más allá de su variant, ni ordenes las acciones a mano.
  • No abras una hoja lateral que la persona no haya pedido.

Accesibilidad

Rol
dialog
Etiquetado
The headline is the sheet's accessible name and is exposed as a heading: the modal dialog's name, or the standard sheet's complementary region's. The close button is named by closeLabel. Each action is named by its own label; confirming and dismissive are not announced, and the actions are read in the order they are placed (BGR-15). The dividers are decorative.
Objetivo mínimo
El área de pulsación cumple el tamaño mínimo de objetivo aunque el tamaño visual sea menor. minimum 32px · pointer 44px · spacing 8px · touch 48px

Teclado

Teclas Acción
Tab / Shift+Tab Moves focus through the close button, the content's focusable elements and the actions, in that order, the actions in the order they are placed (BGR-15). In an open modal sheet it wraps and never leaves the sheet; from an open standard sheet it moves on to the content, and the sheet stays open.
Enter / Space Activates the focused close button (close, reason close) or action (its click; the sheet does not close by itself, SSH-18). The sheet adds no default action to Enter.
Escape Asks an open modal sheet to close (close, reason escape); with stacked overlays, only the topmost. A standard sheet ignores it.

Notas

  • The sheet has no target of its own; its close button and its actions are Tanrei Buttons, which meet the minimum target themselves (BTN-5). comp.side-sheet.actions.gap keeps each action's whole target in a column in the default system.
  • A modal sheet offers at least one action (SSH-16): it holds focus until the person is done, so a person using a screen reader or a switch finds a way to finish the task where they finish reading it, not only a way out. The close button, Escape, back and the scrim still dismiss it. A standard sheet sits beside the content, which the person keeps working in, so it may have no action.
  • Placement is by task, not by code order: in a row the dismissive action is at the start and the confirming one at the end; in a column the confirming one is on top. Focus and screen readers follow the placed order.
  • Dragging is never the only way (WCAG 2.5.7): a modal sheet also closes with its close button, Escape, back or a press on the scrim.
  • The standard form is a layout region, not an overlay: opening it moves focus in and closing it returns focus, and focus leaving it never closes it. The modal form is a modal overlay. The web presents it with a modal <dialog> element (showModal gives inertness and the top layer; its cancel event is cancelled and becomes close with the reason escape); Compose draws it in a window of its own (a Dialog, as TanreiDialog and the modal drawer are) and Flutter as a controlled overlay in a Stack over the page, each with a scrim, trapping focus and answering back; SwiftUI presents it as the dialog is, with a full-screen cover on iOS; React Native presents it in a transparent Modal.
  • Opening a modal sheet by a swipe from the screen edge is left out, as for the drawer: the product opens it from a button.

Generado a partir de specs/side-sheet.spec.json, tokens/sys/measure.tokens.json

De qué se encarga el componente

  • Una hoja modal se expone como un diálogo modal que recibe su nombre de su titular. Una hoja estándar es una región complementaria que recibe su nombre de su titular, la región de referencia (landmark) aside en la web.
  • El titular se expone como un encabezado. El divisor queda oculto para la tecnología de apoyo.
  • Al abrirse, el foco pasa al elemento que marcaste, o a la hoja. Al cerrarse, el foco vuelve al elemento que lo tenía cuando se abrió la hoja. Esto vale para las dos formas.
  • En una hoja modal, Tab y Mayús+Tab dan la vuelta dentro de ella, todo lo que queda detrás está inerte y oculto para la tecnología de apoyo, y Escape, el retroceso y una pulsación en el velo le piden que se cierre. Con superposiciones apiladas, solo se descarta la superior.
  • Desde una hoja estándar, Tab sigue hacia el contenido, y la hoja sigue abierta. Ignora Escape y el retroceso.
  • El botón de cierre es un botón de Tanrei de solo icono que recibe su nombre de closeLabel, y las acciones son botones de Tanrei. Cada uno cumple por sí mismo el objetivo mínimo, y los 16 que hay entre las acciones apiladas conservan entero cada objetivo.
  • El foco y los lectores de pantalla encuentran las acciones en el orden de colocación, el mismo orden que ve una persona vidente. Las acciones se mantienen por encima del teclado en pantalla, así que siguen al alcance mientras la persona escribe en la hoja.
  • Enter y Espacio activan el botón de cierre o la acción que tiene el foco. La hoja no añade ninguna acción predeterminada a Enter.
  • Un arrastre nunca es la única forma de cerrar: también lo hacen el botón de cierre, Escape, el retroceso y el velo.
  • Un texto más grande conserva el ancho de la hoja, hace crecer su cabecera con el titular y desplaza el contenido.
  • Las compilaciones de depuración notifican una hoja sin titular, un botón de cierre sin closeLabel, una hoja modal abierta sin ninguna acción y una variante que el sistema de diseño no tiene.

Carencias conocidas por plataforma:

  • SwiftUI, Compose, Flutter y React Native: dividerVisibility espera a que esas plataformas se pongan al día. Dibujan el divisor bajo la cabecera solo mientras el contenido está desplazado, como hace scroll.
  • Web: en Firefox, una hoja modal desaparece de inmediato en lugar de salir deslizándose, como le ocurre al diálogo. Los arrastres táctiles y los márgenes reales del área segura no están cubiertos por ninguna prueba.
  • SwiftUI no tiene ni un rol de diálogo ni uno de región complementaria: la hoja es un contenedor que recibe su nombre de su titular, con el rasgo modal mientras es modal. SwiftUI no puede dar el foco del teclado a la propia hoja: sin marca, VoiceOver pasa al titular y el foco del teclado se queda donde estaba. Al cerrarse, el foco del teclado no vuelve a ningún elemento; en iOS, la cubierta modal devuelve VoiceOver, y una hoja estándar lo deja ir. Una hoja estándar no se recorta mientras se desliza, así que colócala en el borde de fin de la fila de la ventana. En macOS, una hoja modal cubre solo la vista a la que está unido el modificador, así que únelo a la raíz de la ventana.
  • SwiftUI: el texto sin formato del contenido toma la fuente del cuerpo sin su interlineado, así que no queda colocado exactamente donde lo colocan las demás plataformas. Escape y el gesto de escape de iOS están conectados, pero ninguna prueba los ejecuta.
  • Compose: mientras una hoja modal se cierra, su ventana conserva el foco hasta que termina el movimiento de salida. Hasta que se ponga al día, las acciones se llaman en un RowScope, pero la hoja las dispone ella misma, así que weight y las alineaciones no hacen nada. Sus arrastres se verifican solo con Robolectric, no en un dispositivo.
  • Flutter: una hoja estándar es un contenedor con nombre, no una región de referencia complementaria, porque Flutter 3.32 no tiene ese rol. La hoja todavía no se ha comprobado con TalkBack y VoiceOver en un dispositivo.
  • React Native no puede saber qué elemento tenía el foco, así que, cuando la hoja se cierra, el foco va adonde lo pone la plataforma, no al elemento que la abrió. El Modal, el arrastre, la animación del ancho y los márgenes interiores se verifican solo en Jest, nunca en un dispositivo.
  • Con m3-like, la hoja conserva el ancho del sistema predeterminado y el borde de línea fina de la forma estándar: los valores de Material esperan a que la referencia de fidelidad cubra la hoja lateral.

Qué debes hacer tú

  • Da a cada hoja un headline. Es el nombre de la hoja.
  • Da al botón de cierre, que se dibuja de forma predeterminada, un closeLabel que diga qué cierra, como «Cerrar filtros».
  • Cierra tú la hoja: pon open en false en respuesta a close (el evento close en la web, onClose en Compose, Flutter y React Native). En SwiftUI, la hoja escribe false en el binding por ti.
  • Da a una hoja estándar una forma de cerrarla: su botón de cierre, o un control tuyo con una etiqueta.
  • Da a una hoja modal al menos una acción, marca cada una como confirming o dismissive, y cierra la hoja desde su controlador.
  • Abre la hoja desde un botón visible con su propia etiqueta.
  • Marca el foco inicial donde ayude, como en el primer filtro.