Changelog
Source:NEWS.md
bootstrict (development version)
New widgets
Colour modes follow the operating system.
bs_page(color_mode = "auto")resolves the mode fromprefers-color-schemebefore the page paints, so there is no flash of the wrong theme, andset_bs_color_mode("auto")hands control back to the OS. A mode the user chose is remembered in the browser and survives a reload. The mode in force is reported asinput$bootstrict_color_mode, always"light"or"dark", so the server can render to match. An app that never mentionscolor_modeis untouched.update_bs_carousel(action =)pauses and resumes cycling.bs_scrollspy()takes Bootstrap 5.3’s observer options,root_marginandthreshold;offsetis deprecated upstream, which the documentation now says.bs_tooltip()andbs_popover()acceptplacement = "auto".bs_table()covers the rest of the Bootstrap tables page:stripednow takes"columns"as well asTRUE/"rows"(.table-striped-columns), andhead_variant,group_divider,row_variantandcaption_topgive the generated markup its<thead>colour, the.table-group-divider, the per-row.table-*accents and.caption-top.row_variantis recycled across the rows, andNAleaves one unstyled.bs_navbar_nav(id =)reports its active link asinput$id, likebs_nav(id =), and takes one fromupdate_bs_nav(). Bootstrap has no “navbar page” component – switching content is the application’s job – and this is what makes that pattern expressible; the navigation vignette now shows it.bs_radio_button_input()andbs_checkbox_button_input()render Bootstrap’s segmented control (.btn-check), which had no equivalent: its input is a sibling of its label, withautocomplete="off"and no wrapper, so it cannot come out of shiny’sgenerateOptions(). They are native controls, driven byupdate_bs_toggle_buttons(). A checkbox group reports a character vector,character(0)when nothing is picked.bs_nav(id =)reports thevalueof its active link asinput$idand takes one from the server withupdate_bs_nav();bs_nav_link()gains avalue(defaulting to its text). Clicking a link activates it without jumping to the top of the page, so a nav can be used as a selector.bs_pagination(id =)andbs_pagination_numbered(id =)report the active page asinput$idand take one fromupdate_bs_pagination();bs_page_item()gains avalue. In a numbered pager the arrows now step through the pages and disable themselves at either end, instead of being inert links.bs_dropdown(id =)andbs_nav_dropdown(id =)report their open state asinput$id, andshow_bs_dropdown()/hide_bs_dropdown()/toggle_bs_dropdown()drive them from the server. Bootstrap emitsshown.bs.dropdownand exposes the methods; neither was reachable.bs_alert(id =)reports whether the alert is still on the page asinput$id, andclose_bs_alert()dismisses it from the server. Alerts are interactive in Bootstrap, but reported nothing and had no server helper.bs_nav_dropdown()builds the menu a designer draws in a navbar: an<li class="nav-item dropdown">whose toggle is a.nav-link, per the Bootstrap reference.bs_dropdown()emits a standalone<div class="dropdown">with a<button class="btn">, which is invalid as a direct child of the<ul class="navbar-nav">thatbs_navbar_nav()andbs_nav()produce, and renders as a grey button rather than a nav link — yetbs_navbar()’s own documentation used to recommend it. Takes the same menu items, plusactive/disabled/align/dark.bs_feedback()andset_bs_validation()make validation feedback work. Bootstrap only displays a.valid-feedback/.invalid-feedbackmessage when it is a following sibling of the control carrying.is-valid/.is-invalid. Everybs_*_input()returns its control wrapped in shiny’sdiv.form-group.shiny-input-container, so abs_invalid_feedback()placed after the input was a sibling of the wrapper, never of the control, and stayeddisplay: none— the pattern the forms vignette and the demo app both showed could not work.bs_feedback(input, valid =, invalid =)inserts the messages next to the control itself (once after the last option, for a choice group), takes an optional initialstate =, andset_bs_validation(id, state, message)switches the state and the message text from the server.bs_download_button()andbs_download_link()wrapshiny::downloadButton()/shiny::downloadLink(). shiny hardcodes the Bootstrap 3 class.btn-defaulton the download button, which has no Bootstrap 5 equivalent and leaves the button unstyled; the wrapper swaps it for a real 5.3 variant and takescolor/outline/sizelikebs_button(). shiny’s download plumbing (theshiny-download-linkclass,href/target/downloadand the auto-enable dance) is left intact, so the server side stays a plainshiny::downloadHandler().icondefaults toNULLrather than shiny’s Font Awesome icon, which is outside Bootstrap 5.
New showcase app
-
inst/examples/quakewatch: Quake Watch, a realistic demo (a seismic monitor for the Fiji region built ondatasets::quakes) complementing the exhaustive widget catalogue ininst/examples/demo. It exercises the designer hand-off (bootstrict_theme(variables = "_variables.scss")), UI-declared overlays (offcanvas filter drawer, modal event records, toasts), state-reporting components driven byupdate_bs_*(), and the 5.3 surface (colour modes,.nav-underline,.progress-stacked). Run it withshiny::runApp(system.file("examples/quakewatch", package = "bootstrict")).
Bootstrap upgrade
The package now targets Bootstrap 5.3 (5.3.8, the release it vendors) instead of 5.2, resolving the former 5.2-markup / 5.3-runtime split.
- Colour modes:
bs_page()/bs_page_fluid()/bs_page_fillable()gaincolor_mode(initialdata-bs-themeon the page body) and the newset_bs_color_mode()switches it from the server. The dark variants of navbar (theme = "dark"), dropdown and carousel (dark = TRUE) and the close button (white = TRUE) now emitdata-bs-theme="dark"— their 5.2-era classes (.navbar-dark,.dropdown-menu-dark,.carousel-dark,.btn-close-white) are deprecated in 5.3. - Progress uses the 5.3 markup:
role="progressbar"and thearia-value*attributes live on the.progresstrack (which now carries theid), the inner.progress-baris purely visual, and passing several bars tobs_progress()renders a.progress-stackedgroup.bs_progress_bar()gainsaria_label. - New 5.3 surface:
bs_nav()/bs_tabset()accepttype = "underline",bs_img()gainsobject_fit(.object-fit-*),bs_icon_link()renders the icon-link helper, andbs_navbar(bg =)accepts"body","body-secondary","body-tertiary","white","black","transparent". - 5.3 reference markup details:
bs_card_subtitle()usestext-body-secondary(.text-mutedis deprecated),bs_modal_title()is an<h1 class="modal-title fs-5">again, tab panes carrytabindex="0", tooltips/popovers usedata-bs-title(no more native-tooltip flash), andhelp =text is wired to its control viaaria-describedby.
Breaking changes
-
Bootstrap is vendored. The package no longer depends on
bslib: it ships Bootstrap 5.3.8 underinst/lib/bootstrapand compiles it withsass, so the version reaching the browser is fixed here rather than by whicheverbslibhappens to be installed.bslib::bs_theme(version = 5)only ever meant “the Bootstrap 5 branch bundled by this bslib”, which a future release could move to 5.4.bootstrap_version()reports the vendored release, andbootstrap_dep()returns its HTML dependency for a UI built by hand.Bootstrap’s own build runs autoprefixer after Sass and
sasshas no such step, so the vendored.scsscarries the prefixes, generated with Bootstrap’s own browserslist targets (seedev/vendor-bootstrap.R). bootstrict_theme()returns abootstrict_themerather than abslib::bs_theme(), and takes only...andvariables=. The arguments that were bslib’s rather than SASS’s are gone:bootswatch=andpreset=(Bootswatch ships withbslib, not with Bootstrap), along with the shorthandsbg,fg,base_font,code_font,heading_font,font_scaleandbrand— set the corresponding Bootstrap variables instead ("body-bg","body-color","font-family-base", …). Abslib::bs_theme()passed tobs_page(theme =)is now rejected.-
bs_date_input()andbs_date_range_input()are native<input type="date">fields and no longer delegate toshiny::dateInput(), which loadsbootstrap-datepicker— a third-party stylesheet whose calendar markup (.datepicker,.datepicker-days, …) appears nowhere in the Bootstrap documentation and which a designer’s SASS sheet cannot reach. That contradicted the one promise the package makes, so it is gone; the browser now supplies the calendar, as the Bootstrap 5.3 forms page has it. No third-party widget library is shipped any more, and a test enforces that.What that costs:
format,language,weekstartanddatesdisabledare gone, since the browser owns the presentation and Bootstrap offers no way to ask it for another.shiny::updateDateInput()no longer reaches these controls — useupdate_bs_date_input()/update_bs_date_range_input(), which takeNAto clear a field andNULLto leave it alone.input$idis still aDate,NAwhile the field is empty, and a range is still a length-2Date.bs_date_range_input()gainsseparator, and the container now carries the id while the field takes a derived one, so the two no longer collide. bs_img(),bs_card_img()andbs_figure_img()defaultaltto""rather thanNULL. An<img>with noaltattribute at all is announced by its file name; an empty one marks the image decorative, which is the right default for one the caller did not describe.bs_file_input()emits the Bootstrap 5.3 markup: a plain<input class="form-control" type="file">. shiny builds the Bootstrap 3 compound widget instead — a “Browse” button beside a readonly text box showing the file name, with the real input hidden off-screen — which is not in the Bootstrap 5.3 docs at all, so a designer had no way to draw it. Only shiny’s own element and its progress bar are kept, so uploads andinput$idare unchanged; the browser draws the button and the file name itself. Thebutton_labelandplaceholderarguments are gone with the widget they configured, andsizeandhelpare now accepted like on the other controls.bs_nav_dropdown(id =)now sets the id on the root<li>rather than on the toggle<a>, so it addresses the widget the way every other interactive constructor does and the new state reporting and server helpers can find it. This only affects code written against the unreleasedbs_nav_dropdown().bs_table(data)now renders a data frame’s row names as the reference<th scope="row">header cell, sobs_table(head(mtcars))keeps the car names it used to drop. This adds a leading column for frames that carry real row names; automatic row names (a tibble, a freshly built data frame) are unaffected. The newrownamesargument forces the behaviour either way.bs_page_fillable()loses itsfillableargument:bslib::page_fillable()has no such parameter, so the value leaked into the page markup as an invalidfillableHTML attribute and controlled nothing.bs_modal(backdrop = FALSE)now renders a modal with no backdrop (data-bs-backdrop="false"), matching Bootstrap andbs_offcanvas(). Usebackdrop = "static"for a backdrop that does not dismiss on outside click (the previous behaviour ofFALSE).bs_input_group()andbs_floating_label()now raise an error when given an input whose Shiny binding lives on its container (bs_date_input(),bs_date_range_input(),bs_file_input(),bs_radio_input(),bs_checkbox_group_input()). Previously they silently emitted a dead control with no id, binding, or dependencies.Layout constructors now validate their scales instead of emitting non-existent classes:
bs_col()spans (1-12,"auto",TRUE),bs_row(cols=)(1-6,"auto"), gutters and stackgap(0-5),offset(0-11),order(0-5,"first","last"), headinglevels (1-6),bs_pagination_numbered(current=)(1..n),bs_color_input(value=)(#rrggbb), andbs_tabset(selected=)must match a panel value.bs_notify_toast()requires plain-textbody/title(they are rendered viatextContent; tags now raise an error instead of displaying raw markup or[object Object]), and its...must be empty.
Bug fixes
The natively built controls no longer carry shiny’s classes.
bs_range_input(),bs_color_input(),bs_date_input(),bs_date_range_input()and the two toggle-button controls emitted.shiny-input-containerand.form-groupalthough they build their own markup and their own binding. Neither is read by shiny’s JavaScript, but shiny’s stylesheet caps.shiny-input-containeratwidth: 300px— so a segmented button group was clipped to 300px by CSS that is not Bootstrap’s, and.form-rangewas not the full-width control Bootstrap draws..form-groupis a dead Bootstrap 3 class besides.bs_file_input()keeps.form-groupalone: shiny’s binding finds the upload progress bar withclosest("div.form-group").data-bs-themeis now set on the document root rather than the<body>. Bootstrap’scolor-schemedeclaration is what tells the browser to paint scrollbars and native controls dark, and it only reaches them from<html>.Dropdown options now reach the element Bootstrap reads them from. Passing
data-bs-auto-closethrough...put it on the.dropdownwrapper, while Bootstrap only ever looks at the toggle, so it did nothing.auto_close,offsetandreferenceare arguments and land on the toggle.bs_carousel()no longer emits a duplicateddata-bs-ride. Passing it through...produceddata-bs-ride="carousel true", which is not a value.autoplay = "resume"is Bootstrap’s"true", andwrap,keyboard,pauseandtouchare arguments.-
A batch of small markup defects:
-
bs_tab_panel()andbs_accordion_panel()applied their named...as body text instead of attributes, sodata-bs-theme = "dark"showed up as visible text in the panel. - A tag title gave an escaped value:
bs_tab_panel(span("Home"), …)reporteddata-value="<span>Home</span>". The value is now taken from the title’s text, and a title with no text asks for an explicitvalue. -
bs_input_group()dropped the.form-textnode but kept the control’saria-describedby, leaving a dangling ARIA reference. -
bs_floating_label()put aplaceholderon a<select>, which has no such attribute. -
bs_collapse_trigger()given several targets kept only the first indata-bs-targetwhile listing them all inaria-controls; Bootstrap resolves that attribute withquerySelectorAll, so the documented multiple-target pattern now works. -
bs_scrollspy()inside a hidden tab pane never activated and reported nothing: Bootstrap measured a zero-height container. It is refreshed when its tab is shown. -
bs_nav_link(disabled = TRUE)kepthref="#"withouttabindex="-1", so a disabled link stayed keyboard-activatable. -
bs_table(align =)was not validated, so any string became a non-existent.align-*class. - A hand-composed
bs_modal()had no accessible name at all;aria-labelledbywas only ever set by thetitleshortcut, and now points at a composedbs_modal_title().
-
Forgetting the leading
idof an interactive widget now raises an error instead of rendering silently.bs_tabset(),bs_accordion(),bs_carousel(),bs_collapse(),bs_modal(),bs_offcanvas()andbs_toast()take their id first, so the first child landed onid:bs_tabset(bs_tab_panel("A", "a"))rendered an empty<ul>whose id was the deparsed panel object. On an API whose central convention is “the id comes first”, that is the mistake everyone makes.bs_tabset(),bs_accordion(),bs_carousel()andbs_progress()accept children built withlapply(). Generating panels in a loop is the usual thing to do in a data-driven app, anddev/CONVENTIONS.mdsays to let htmltools flatten lists, but each constructor validated the type of its children first and so saw the list itself:bs_tabset("t", lapply(...))failed with “All...arguments must bebs_tab_panel()s”. Children are now flattened before the check, without descending into a panel object, which is itself a classed list.bs_date_range_input()no longer renders small whatever you asked, and its separator is a real Bootstrap 5 add-on. shiny hardcodes.input-group-smon the group, and wraps the ” to ” text in a Bootstrap 3<span class="input-group-addon input-group-prepend input-group-append">; add-ons must be direct children of.input-groupunder Bootstrap 5, or the corner rounding between the two fields breaks. The size is now decided by asizeargument.shiny::updateRadioButtons()andshiny::updateCheckboxGroupInput()no longer strip the Bootstrap 5 markup offbs_radio_input()andbs_checkbox_group_input(). Both replace the whole options block with HTML generated server-side byshiny:::generateOptions(), which has no theme branch and always emits Bootstrap 3 (<div class="radio"><label><input>); bootstrict’s enhancement ran once, at render time, so the first update withchoicessilently lost.form-check,.form-check-inputand.form-check-label. The classes are now put back client-side, which keeps the documented promise that shiny’s own updaters work unchanged —inlineandreversecannot be recovered from the replaced HTML, so they are recorded on the container.The package tarball no longer ships an internal Posit Connect deployment record (
inst/examples/quakewatch/rsconnect/…/quakewatch.dcf, carrying a server host name and a user name)..Rbuildignorenow excludes anyinst/examples/*/rsconnectdirectory.bs_table()no longer renders the wrong values for a tibble. Cells were extracted withdata[i, j], which drops to a vector for adata.framebut keeps a 1x1 frame for a tibble, soas.character()rendered the underlying storage: a factor as its integer code, aDateas its day number. Cells are now read from the column.bs_table()formats numbers the way a reader expects.as.character()rendered100000as"1e+05"and1/3with fifteen significant digits.An
.input-group-textaddon holding a checkbox is no longer destroyed.bs_input_group()descended into any child containing a.shiny-input-containerand returned the first control it found, discarding everything around it, sobs_input_group(bs_input_group_text(bs_checkbox_input("cb", NULL)), …)lost its<span class="input-group-text">and left the checkbox as a bare sibling of the text input. Addons are now passed through untouched, andbs_input_group_text()builds Bootstrap’s documented addon: a lone.form-check-input.mt-0, without the.shiny-input-containerand.form-checkwrappers whose indent is meant for a labelled control in a form.bs_list_unstyled()andbs_list_inline()no longer nest an<li>inside an<li>. Every child was wrapped in a fresh<li>, including one that already was an<li>— the usage the content vignette documents for richer items. The HTML parser closes the outer item at the inner start tag, so what survived was an empty<li class="list-inline-item">followed by a bare<li>carrying none of the list’s classes, which breaks the inline layout and made the Bootstrap nested-list example unreachable. An<li>child is now passed through (gaining.list-inline-itemwhere it applies), and a child that is a bare list (anlapply()result, atagList()) is expanded into one item per element instead of being wrapped whole in one<li>.bs_progress(height =)no longer wipes out a stacked group. Each segment received two separatestyleattributes, and htmltools joins duplicated attributes with a space rather than"; ", so the rendered attribute wasstyle="width: 15% height: 10px"— a single malformed declaration the browser drops entirely. The segments came out zero-width (invisible) as soon asheightwas supplied. Width and height are now one declaration.The close button of a responsive
bs_offcanvas()now closes it. A responsive panel carries.offcanvas-{bp}instead of.offcanvas, and Bootstrap’s dismiss handler resolves its target asgetElementFromSelector(this) || this.closest(".offcanvas"): with no explicit target it found nothing, threwTypeError: Cannot read properties of undefined (reading 'backdrop')and left the panel open. The header close button now carries an explicitdata-bs-target(escaped, so module ids work), which is harmless on a plain offcanvas since it resolves to the same element.A hand-composed
bs_modal()no longer nests its header and footer inside a.modal-body.bs_modal()’s own documentation invites composing the dialog withbs_modal_header()/bs_modal_body()/bs_modal_footer(), but every unnamed child was wrapped in a.modal-bodyregardless, so the documented path produced.modal-content > .modal-body > (.modal-header, .modal-body, .modal-footer)— invalid structure that breaks the sticky header and the scrollable body. Children that already carry one of those classes are now emitted as siblings of.modal-content; bare children are still wrapped, in place, so the two styles can be mixed.Re-rendering a
bs_modal()orbs_offcanvas()while it is open no longer leaves the page permanently unscrollable. Both bindings tore the widget down withinst.hide(); inst.dispose();in the same tick, buthide()is transition based: the synchronousdispose()aborted Bootstrap’s teardown, so the backdrop node was removed whilebody.modal-openand the inlineoverflow: hidden; padding-right: …scroll lock stayed behind. Any overlay living inside arenderUI()froze the page as soon as it was re-rendered while shown. Disposal now waits forhidden.bs.modal/hidden.bs.offcanvas(Bootstrap fires it on a timeout even for a detached element), and a safety net drops the body scroll lock once nothing is shown.-
bootstrict_theme()now puts each value in the Sass layer that can compile it, instead of handing everything tobslib::bs_theme()as a named argument. Two forms used to fail outright: a theme colour defined from another variable — the way Bootstrap ships its own defaults — (bootstrict_theme(secondary = "$gray-600")) aborted on bslib’s HTML-colour validation, and any value derived from a Bootstrap variable ("link-hover-color" = "shade-color($primary, 20%)") aborted at compile time withUndefined variable: "$primary", because named arguments land in the defaults layer, which is emitted before Bootstrap’s own variables.Values built from literals or from the sheet’s own variables now go to the defaults layer, in sheet order, so
$primary: $brand-orangeworks and still feeds$theme-colors; values referring to one of Bootstrap’s variables go to the declarations layer, where those exist.bs_theme()keeps the arguments that are not Sass variables (bg,fg, the fonts,preset,bootswatch). A theme colour redefined from one of Bootstrap’s own variables now compiles but lands after$theme-colorsis built, so it does not restyle.btn-secondary; this is documented onbootstrict_theme(). -
parse_scss_variables()no longer drops most of a designer’s sheet. It matched$name: value;line by line, so any declaration spanning several lines matched nothing and was discarded without a warning — that is the shape of every Bootstrap map ($theme-colors,$grid-breakpoints,$spacers,$container-max-widths,$font-sizes,$utilities). On Bootstrap’s own_variables.scssit read 916 of 963 declarations andtheme-colorswas absent. Stripping//comments with no notion of strings also truncated any value containing one, silently losing$web-font-pathandurl("https://…"); a;inside a quoted string cut the value short; and a final declaration with no trailing;was lost.The file is now scanned instead of split into lines: declarations may span any number of lines,
;/////* */inside a quoted string or an unquotedurl()are read as data,#{}interpolation is not mistaken for a rule block, and a rule block no longer bleeds into the declaration that follows it. bs_tooltip()/bs_popover()no longer disable the Shiny input they decorate. Both were initialised through aShiny.InputBinding, and since Shiny binds at most one input per element and later registrations take precedence, the bootstrict binding claimed the element and the real one never bound:bs_tooltip(bs_button("save", "Save"), "Ctrl+S")leftinput$savepermanentlyNULL. Tooltips and popovers are now initialised from the DOM (an initial sweep plus aMutationObserver, sorenderUI()/insertUI()content is still covered) and disposed when their element is removed.bs_tooltip()/bs_popover()no longer break the tag they decorate when it is already a data-API trigger (bs_modal_trigger(),bs_offcanvas_trigger(),bs_collapse_trigger(), a dropdown toggle…). They used to append a seconddata-bs-togglevalue (e.g."offcanvas tooltip"), which Bootstrap’s exact[data-bs-toggle="offcanvas"]delegated selector no longer matched — the trigger went dead. The existing attribute is now left untouched; tooltip and popover initialisation never relied on it (it is driven bydata-bootstrict-tip).Named arguments in
...are now applied to the documented element: thebs_dropdown()wrapper (they were rendered as visible page text), thebs_modal()root (they landed on.modal-body), and thebs_navbar()<nav>(they landed on the collapse<div>).Extra attributes passed to
bs_checkbox_input()/bs_switch_input()are no longer silently dropped, andbs_file_input()’s...now lands on the real<input type="file">instead of the readonly display box (internalhas_class()only saw the first of severalclassattribute entries).Attributes passed through
...now replace a same-named attribute set by shiny instead of merging with it (bs_text_input("x", type = "email")no longer renders the invalidtype="text email").First server-driven update on a never-toggled collapse/accordion panel no longer does the opposite of what was asked: Bootstrap Collapse instances are now created with
{toggle: false}(the constructor defaulttoggle: truetoggled the panel before the requested action ran).Responsive offcanvas (
bs_offcanvas(responsive =)) now binds its state input: the JS selector matched.offcanvasonly, never.offcanvas-{bp}, soinput$idwas never registered. Above the breakpoint the inline-shown panel now reportsTRUE.bs_carousel(autoplay = FALSE)now omitsdata-bs-rideentirely; it used to emitdata-bs-ride="true", which resumes autoplay after the first user interaction.update_bs_list_group(id, selected = NULL)is now the documented no-op; it used to clear the whole selection and resetinput$id. Aselectedvalue matching no item warns and leaves the selection unchanged.update_bs_accordion():open = TRUEnow opens all panels (it used to send the literal string"TRUE");open/close = FALSEare no-ops.bs_button(href =, disabled = TRUE)now renders the.disabledclass andtabindex="-1"(the anchor was still clickable).Accordion state no longer leaks between nested accordions (panel discovery and Bootstrap events are now scoped to the accordion’s own panels), and an accordion with every panel closed reports
NULLinstead oflist().Progress: percentages are clamped to 0-100 as documented; a min/max-only update recomputes the width; a colour update no longer strips
bg-opacity-*/bg-gradientclasses (see also the Bootstrap 5.3 markup restructure above).Ids containing CSS-special characters (e.g. dotted module namespaces) no longer break declarative wiring: every generated
data-bs-target/data-bs-parent/ triggerhrefselector is now CSS-escaped.Dynamic UI lifecycle: Bootstrap instances are disposed when Shiny unbinds a widget (modals/offcanvas hide first — no more stuck backdrops after a
renderUI()re-render),bs_notify_toast()disposes each toast on hide (it leaked a detached DOM node and instance per notification), the list-group click handler unbinds cleanly (it used to stack up across unbind/rebind cycles), tooltip/popover auto-ids no longer collide (a shared timestamp gave duplicate ids) and their instances are disposed on unbind, and carousels/scrollspys inserted viarenderUI()are now initialised at bind time (Bootstrap only scans on page load).bs_range_input()dragging is debounced as documented (inputevents now route through the rate policy;changestill submits immediately), and the range/color JS selectors are scoped to bootstrict’s owndata-bootstrictmarker so hand-written Bootstrap markup is not hijacked.bs_range_input()andbs_color_input()participate in Shiny bookmarking (restoreInput()).parse_scss_variables()strips multi-line/* ... */comments (variables inside a commented block were parsed as real) and reads every declaration on a line, not just the first.use_bootstrict_golem()scaffolds_variables.scss(SCSS syntax, whichparse_scss_variables()reads) instead of_variables.sass(indented syntax, which silently parsed to nothing).Radio/checkbox group labels get
.form-label, per-option and single checkbox labels get.form-check-label, andbs_file_input()drops the leftover Bootstrap 3/4 markup (.input-group-btnwrapper, BS3 progress animation classes).Selectable / action list groups no longer emit invalid HTML (
<li>items inside their<div>container are converted to<div>s); disabled anchor items gettabindex="-1".Modals and offcanvas with a
titlenow wirearia-labelledbyto the title element; vertical tabsets setaria-orientation="vertical";bs_collapse_trigger()gainsexpandedfor a correct initialaria-expanded/.collapsedstate;bs_breadcrumb(divider =)escapes quotes/backslashes.Responsive
bs_dropdown(align = list(...))now setsdata-bs-display="static"on the toggle, without which Bootstrap ignores the responsive alignment classes.
New features
-
update_bs_list_group(id, selected = character(0))clears the selection (deselects every item and resetsinput$idtoNULL).selected = NULLremains the no-op that leaves the current selection untouched, so a group can now be cleared server-side without the surprise reset that the pre-NULLno-op behaviour carried. -
bs_checkbox_input(),bs_switch_input(),bs_date_input()andbs_date_range_input()gain thehelpargument the other inputs already had. -
bs_notify_toast()gainsautohide(useFALSEfor a persistent notification); header-less notifications now include a close button (they were undismissable), dark-background notifications get the dark-context close button (data-bs-theme="dark"), and the auto-created container is anaria-liveregion. -
bs_scrollspy()gains anid(auto-generated by default) and reports the active section’s link asinput$id. -
bs_offcanvas_trigger()delegates tobs_button()likebs_modal_trigger()(it was a bare unstyled.btn). - Server messages targeting a missing element now
console.warnwith the offending id instead of failing silently (the most common module namespacing mistake becomes visible). -
bootstrict_dep()is built once per session and cached.