fix: Reorder bundle so bslib wins cascade ties against shiny.scss#2256
Merged
Conversation
….scss Drop down from `bs_bundle()` to `sass::sass_bundle()` directly so `shiny.scss` can be listed before `theme` in the bundle. bslib's `bs3compat` is baked into `theme`, and `bs_bundle()` always stacks named layers on top of `theme`, putting bslib's BS5 overrides earlier in the compiled CSS than R-shiny's shiny.scss — the opposite of an R-shiny + bslib app, where shiny.css loads as its own <link> before bslib's bundle. Same-specificity ties therefore went to shiny.scss by source order, defeating bslib's bs3compat overrides. After the reorder, bslib's bs3compat emits later in the file and wins cascade ties. The only same-specificity, same-element conflict between bs3compat and shiny.scss in BS5 mode is the radio/checkbox label-margin rule; the audit found no other rules whose winner flips. Net effect: vertical radio/checkbox groups now use bslib's calc value instead of shiny.scss's `-10px`; inline groups still use shiny.scss's `-1px` (specificity 0-3-1 beats bs3compat's 0-2-1 without rstudio/bslib#1308's doubled-class compound). Pins bslib and shiny in `scripts/_pkg-sources.R` to the SHAs that were used in main's last vendor so the regenerated assets isolate the cascade-reorder change with no upstream drift.
The previous commit pinned these to specific SHAs so the cascade- reorder vendor diff was free of upstream drift. Restore the moving-target `@main` pins so the next `make upgrade-html-deps` pulls current upstream.
3 tasks
gadenbuie
approved these changes
May 21, 2026
Collaborator
gadenbuie
left a comment
There was a problem hiding this comment.
LGTM! Thanks for digging so deeply into this one!
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.
Summary
Reorder layers in
bs_full_theme()so R-shiny'sshiny.scssemits before bslib in the compiledbootstrap.min.css. This matches the cascade order an R-shiny + bslib app sees (whereshiny.cssis added as its own<link>before bslib's bundle), and lets bslib andbs3compatrules win same-specificity ties againstshiny.scss.Solution
bs_bundle()requiresthemefirst (its first argument is asserted to be abs_theme), so drop down tosass::sass_bundle()directly and reapply thebs_themeclass — exactly mirroringbs_bundle()'s implementation, just without the required order.Shiny & bslib versions were pinned to their last vendored version for the purpose of making sure this diff is just changes from the order change, not any of the other bs5 changes that will be covered in later PRs or the shiny changes that will be covered in later PRs.
Visible effect
NOTE: These effects are intended!
ui.input_radio_buttons()/ui.input_checkbox_group()label-to-options gap-10px(shiny.scss base)calc(-.15em - var(--bs-border-width))(bslib bs3compat, ~ -3.4px)-1px(shiny.scss inline)-1px(shiny.scss inline) — unchanged; bslib's bs3compat rule is at 0-2-1 specificity and still loses to shiny's 0-3-1 inline rule. Will flip too once rstudio/bslib#1308 (which lifts bslib's rule to 0-3-1 with a doubled-class compound) merges.Why this works: cascade-order analysis
Click to expand
The bug
bslib
bs3compatis meant to override R-shiny'sshiny.scssfor several Bootstrap 5 backward-compatibility cases. In py-shiny those overrides were losing same-specificity ties because the vendoredbootstrap.min.cssplaced bslib's bs3compat beforeshiny.scssin the file — the opposite of an R-shiny + bslib app's head order.Where the order came from
bs_full_theme()inscripts/_functions_sass.Rcalledbs_bundle(theme, bslib = ..., shiny = ..., ...). bslib's bs3compat is baked into thethemeargument, andbs_bundle()always stacks named layers on top oftheme. So bs3compat's rules emitted first,shiny.scsslater. Same-specificity ties went toshiny.scssby source order.Byte-level confirmation
In the regenerated
shiny/www/shared/bootstrap/bootstrap.min.css:shiny.scss-10px(.shiny-input-radiogroup label ~ .shiny-options-group)shiny.scss-1pxinline (.shiny-input-radiogroup.shiny-input-container-inline label ~ .shiny-options-group)bs3compatcalc (.shiny-input-radiogroup label ~ .shiny-options-group)shiny.scssnow sits before bslib in the file; bslib wins all same-specificity ties.Collateral-damage audit
Only one selector pair has same-specificity + same-element overlap between
bs3compatandshiny.scssin BS5 mode:.shiny-input-{checkboxgroup,radiogroup} label ~ .shiny-options-group(0-2-1 in both).bs3compatsetsmargin-top: calc(-.15em - var(--bs-border-width)).shiny.scssbase setsmargin-top: -10px.-10px.Other classes that look shared but don't actually collide:
shiny.scssusage.checkbox,.radio.qt5or.qtmac(Qt browser scopes).well.well .shiny-input-container(descendant).active,.show,.in,.nav&-active, comments).shiny-options-group.shiny-input-{kind}shiny.scss's.radio/.checkboxusages are all prefixed with.qt5/.qtmac(Shiny Server browser scopes — different element scope).bs3compat's.radio/.checkboxstyling is wrapped in@if $bootstrap-version == 4and doesn't compile for BS5.Net effect: only the vertical radio/checkbox label-margin changes (and to the value
bs3compatintended); no other styles are affected.Relationship to other PRs
make upgrade-html-depsto inherit the layer order.make upgrade-html-deps, inline will also flip to the calc value. This will fix the original radio button spacing issue in [Bug]: Spacing for Radio Buttons #2236Test plan
ui.input_radio_buttons()/ui.input_checkbox_group()use the calc gap (slightly tighter than the previous-10px)