GitHub

Navigation

File Viewer navigation patterns for thumbnails, attachments, segments, and source-linked navigation panels.

FileViewerSidebar is the optional rail inside FileViewerContent. It belongs to the File Viewer frame: FileViewer, FileViewerHeader, FileViewerContent, FileViewerSidebar, and FileViewerInset.

Use it for document-adjacent panels: page sections, attachments, source fields, thumbnails, and other workflow rails that sit beside a viewer inset. Do not use the app-shell SidebarProvider pattern for these rails.

Viewer Sidebar Example

This is the shape used across file-backed viewers: FileViewerProvider owns sidebar open state, FileViewer owns mode resolution, and FileViewerSidebar owns the rail shell and accessible rail label. The navigation component inside the rail owns the row model and interaction. Here, SegmentSidebar owns section rows while FileViewerDocument owns the document renderer.

Prospectus review
ViewerSidebar owns the rail; SegmentSidebar owns the rows.

Composition

<FileViewerProvider source={source} defaultSidebarOpen>
  <FileViewer sidebarMode="auto">
    <FileViewerHeader>
      <FileViewerSidebarTrigger />
      <FileViewerTitle className="flex-1" />
      <FileViewerControls className="ms-auto" />
    </FileViewerHeader>
    <FileViewerContent>
      <FileViewerSidebar aria-label="Document sections" width="18rem">
        <FileViewerSidebarHeader>Sections</FileViewerSidebarHeader>
        <FileViewerSidebarContent>
          <SegmentSidebar
            segments={segments}
            interaction={interaction}
            currentPage={currentPage}
            onSelect={(segment) => scrollToPage(segment.pages[0])}
          />
        </FileViewerSidebarContent>
      </FileViewerSidebar>
      <FileViewerInset>
        <FileViewerViewport>
          <FileViewerDocument />
        </FileViewerViewport>
      </FileViewerInset>
    </FileViewerContent>
  </FileViewer>
</FileViewerProvider>

Ownership Boundary

  • FileViewerProvider owns sidebar open state and controlled/uncontrolled state props.
  • FileViewer owns sidebar mode resolution, trigger wiring, the single-primary-sidebar contract, and the root motion store for inline geometry.
  • FileViewerSidebar owns spatial placement, declared width, side override, collapse behavior, and the rail's accessible label.
  • FileViewerInset owns the main file inset and document frame beside the rail.
  • SidebarList* owns providerless grouped-row grammar inside a rail.
  • SegmentSidebar owns segment row state, preview interaction, current-page highlighting, and select callbacks.
  • AttachmentSidebar owns file attachment rows, thumbnails, selected state, disabled rows, and empty state.

Do not mount SidebarProvider inside File Viewer. That provider is for app-shell navigation: persistence, global shortcuts, mobile sheet projection, and workspace menus. File Viewer rails use FileViewerSidebar for placement and SidebarList* for row grammar.

Domain Rails

Use a named domain sidebar when the rows have semantics:

  • SegmentSidebar for split and partition segments. It coordinates with legends and page timelines through useSegmentInteraction.
  • AttachmentSidebar for files attached to an email, message, or workflow. It renders FileThumbnail rows and lets the caller own selected attachment id.
  • source-field sidebars in extraction workflows should use the same FileViewerSidebar placement contract and a domain row model, not an app navigation menu.

Use raw SidebarList* primitives only when you are creating a new domain rail and need grouped rows without app-sidebar behavior.

Use FileViewerSidebarContent and the sidebar section primitives when a custom rail needs File Viewer spacing but not menu semantics.

FileViewer sidebarMode="auto" starts in overlay mode until the viewer has a measured width, then resolves to inline or overlay. Use fixed modes only when a workflow requires it:

<FileViewerProvider source={source} defaultSidebarOpen>
  <FileViewer>
    <FileViewerContent>
      <FileViewerInset>
        <FileViewerViewport>
          <FileViewerDocument />
        </FileViewerViewport>
      </FileViewerInset>
      <FileViewerSidebar aria-label="Attachments" side="right" width="20rem">
        <AttachmentSidebar items={attachments} selectedId={selectedId} />
      </FileViewerSidebar>
    </FileViewerContent>
  </FileViewer>
</FileViewerProvider>

FileViewerSidebarTrigger is disabled until a FileViewerSidebar registers with the nearest FileViewer frame. In overlay mode, Escape closes the sidebar and focus returns to the trigger.

For inline rails, the document and sidebar resize from one FileViewer motion clock. The motion store writes the sidebar gap and document surface transform from the same geometry frame so inherited root styles do not invalidate the full viewer tree. Renderers use the document-frame contract for fit-width scale, anchoring, and virtualization.

Structural Attributes

File Viewer sidebar primitives expose stable attributes for local styling and tests:

  • data-slot="file-viewer-header", file-viewer-content, file-viewer-sidebar, file-viewer-sidebar-header, file-viewer-sidebar-content, file-viewer-sidebar-footer, file-viewer-inset, and file-viewer-sidebar-trigger;
  • data-file-viewer-sidebar-mode="inline" | "overlay";
  • data-file-viewer-sidebar-open="true" | "false";
  • data-file-viewer-sidebar-state="expanded" | "collapsed";
  • data-file-viewer-sidebar-side="left" | "right";
  • data-collapsible and data-side.

Put domain meaning in the named rail component and accessible label: aria-label="Document sections", aria-label="Email attachments", or aria-label="Source fields".

The lower-level shared viewer primitives also emit data-viewer-* attributes, but file-viewer examples and app-level styling should prefer the namespaced data-file-viewer-* contract.

Source

file-viewer-sidebar.tsx