feat: add offcanvas() panels#1328
Merged
Merged
Conversation
Add offcanvas() deferred object with as.tags/print rendering of the <bslib-offcanvas> custom element, plus show/hide/toggle_offcanvas() server verbs. Add offcanvas.scss to pin an optional footer via flexbox. Client-side JS binding + custom-message handler follow in subsequent commits.
Add <bslib-offcanvas> Lit web component (Shiny input binding wrapping bootstrap.Offcanvas; reports open/closed state; handles hide/toggle messages) and the bslib.show-offcanvas custom-message handler (insert- if-absent, bind, show; anonymous panels self-destruct on hide). Rebuild dist bundles.
Add test-offcanvas.R, roxygen Module IDs section, _pkgdown.yml and NEWS.md entries, and generated man pages. Tests surfaced three render-time bugs, now fixed: - normalize_offcanvas_placement() errored on the default placement vector (arg_match0 on the first element instead of arg_match on the vector) - splice named ... attributes via rlang::list2() (list() is not dynamic-dots) - wire the trigger via tagAppendAttributes on its last element (tagQuery()$last() does not exist) Also suppress the title-NULL accessibility warning when an aria-label or aria-labelledby is supplied via ....
- offcanvas_wire_trigger() wraps a bare-string trigger (or bare-string last element of a tagList) in a <span> before attaching Bootstrap wiring, mirroring getOrCreateTriggerEl() in _utilsTooltip.ts. - Document that show_offcanvas() reveals an existing id'd panel without re-rendering its content. - Align server-verb `...` roxygen with show_toast() wording.
- Dispose the Bootstrap Offcanvas instance in disconnectedCallback so anonymous (temporary) panels removed on hide don't leak the instance, backdrop, and focustrap (matches popover/tooltip teardown). - Narrow receiveMessage on data.method directly, dropping the redundant ToggleMessage cast. - Note that .offcanvas styling overrides the :host display fallback.
Adds tests for footer rendering, the close_button / header-omission branches, width/height CSS variables, the print preview, and tagList/bare-string trigger wiring. Extracts offcanvas_preview_tags() from print.bslib_offcanvas() so the preview rendering can be tested without print() opening a browser. Fixes a bug surfaced by the new tests: the trigger branch used find() (descendants only) to add the .show class, but the panel is a top-level sibling of the trigger, so it was never shown in the preview. Use filter() instead.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new Bootstrap 5 offcanvas component to bslib, including an R API (offcanvas()) and Shiny server controls (show_offcanvas(), hide_offcanvas(), toggle_offcanvas()), backed by new client-side web components and message handlers plus styling and documentation updates.
Changes:
- Introduces
offcanvas()+ server verbs with lifecycle semantics (persistent vs temporary panels). - Adds a
<bslib-offcanvas>web component (Shiny input binding) and abslib.show-offcanvascustom message handler for server-defined insertion. - Adds SCSS styling, docs/reference entries, NEWS entry, and comprehensive test coverage.
Reviewed changes
Copilot reviewed 12 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
R/offcanvas.R |
New offcanvas API, tag rendering, trigger wiring, preview, and server verbs. |
tests/testthat/test-offcanvas.R |
Unit tests for constructor defaults, tag output, a11y wiring, sizing, and server verb messaging. |
srcts/src/components/webcomponents/offcanvas.ts |
New <bslib-offcanvas> Shiny input web component wrapping Bootstrap Offcanvas. |
srcts/src/components/webcomponents/index.ts |
Registers the new web component. |
srcts/src/components/offcanvas.ts |
Adds bslib.show-offcanvas custom message handler to insert/show server-defined panels. |
srcts/src/components/index.ts |
Ensures the new offcanvas message handler module is loaded. |
inst/components/scss/offcanvas.scss |
Styling for offcanvas title and footer. |
inst/components/dist/web-components.js |
Built web-components bundle update to include <bslib-offcanvas>. |
inst/components/dist/web-components.min.js |
Minified web-components bundle update. |
inst/components/dist/components.js |
Built components bundle update to include bslib.show-offcanvas handler. |
inst/components/dist/components.min.js |
Minified components bundle update. |
inst/components/dist/components.css |
Built CSS update to include offcanvas styles. |
pkgdown/_pkgdown.yml |
Adds Offcanvas reference section to pkgdown site. |
man/offcanvas.Rd |
Generated documentation for offcanvas(). |
man/show_offcanvas.Rd |
Generated documentation for server verbs. |
NEWS.md |
Adds a NEWS entry for the new feature. |
NAMESPACE |
Exports new functions and S3 methods. |
DESCRIPTION |
Adds offcanvas.R to collate list. |
man/navbar_options.Rd |
Minor formatting tweak in docs. |
inst/examples-shiny/code-editor/examples/r.r |
Fixes assignment operator in an example snippet. |
Files not reviewed (2)
- man/offcanvas.Rd: Generated file
- man/show_offcanvas.Rd: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
cpsievert
approved these changes
Jun 30, 2026
cpsievert
left a comment
Collaborator
There was a problem hiding this comment.
Nice, thanks! Copilot left some nitpicky feedback, but the overall API here looks great.
Demonstrates the local-id contract (ns() only in the UI; server verbs and input[[id]] use the module-local id) with a slider, verbatim output, and self-closing button inside the panel.
This was referenced Jul 1, 2026
Merged
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1194
Summary
Adds
offcanvas()plus server verbsshow_offcanvas()/hide_offcanvas()/toggle_offcanvas()for Bootstrap offcanvas panels (sliding side/top/bottom drawers).offcanvas()returns a deferredbslib_offcanvasobject (same pattern astoast()/sidebar()) supporting three usage modes, selected by whethertrigger/idare supplied. State and lifecycle are governed byid: an explicit id gets a liveinput$<id>logical binding and persists in the DOM across hide/show; an anonymous panel self-destructs (unbinds + removes) on hide. Client-side binding is a<bslib-offcanvas>web component wrappingbootstrap.Offcanvas, registered alongside the existing popover/switch web components;show_offcanvas()rides a custom message (insert-if-absent + show, required since no client binding exists yet for server-only panels) whilehide/toggleridesendInputMessage. Styling lives ininst/components/scss/offcanvas.scss, auto-picked-up by the existing SCSS glob (no new dependency registration needed).Verification
(a) Trigger + panel in the UI -- clicking the trigger reveals the panel client-side, no server round-trip:
(b) Id'd panel in the UI, controlled from the server:
(c) Server-defined panel, revealed on demand (no UI declaration needed):
In each case the panel slides in from the right edge of the viewport (default
placement);input$oc(mode b) flips toTRUE/FALSEas it's shown/hidden.