You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#29 ships the bootstrap theme as CSS only, with Alpine driving every interactive component and bootstrap.bundle.min.js deliberately absent. That is the right call, but it is currently an undocumented one: the reasoning exists only in a research artifact (claudedocs/research_bootstrap_js_convention_2026-07-29.md), which is a record of what was investigated, not a record of what was decided.
Two concrete gaps follow from that, and this ticket closes both.
Consumers have no guidance. Bootstrap ships 13 JS-driven components. cf-ui wraps 5 of them. A consumer who wants a toast, an offcanvas, a carousel, or scrollspy has no documented answer for whether they may add Bootstrap's bundle themselves, and no warning about what happens if they do.
The decision has no expiry mechanism. It was made against Bootstrap 5 and is correct for Bootstrap 5. Bootstrap 6 changes the premises, and nothing in the repo will notice.
Why this needs a tripwire rather than a note
Verified directly against v6-dev (secondary sources on this are unreliable — several claim v6 already moved to native <dialog> citing twbs#41751, which is closed and was never merged; twbs#39191 is closed NOT_PLANNED):
modal.js and _modal.scss are gone. They are replaced by dialog.ts / dialog-base.ts / _dialog.scss, built on HTMLDialogElement.showModal().
_dialog.scss on v6-dev defines .dialog, .dialog-header, .dialog-body, .dialog-footer, .dialog-title, .dialog-scrollable, .dialog-fullscreen, .dialog-static, .dialog-nonmodal, .dialog-instant, .dialog-slide-up, .dialog-slide-down, .dialog-swap-in, .dialog-open. Not one .modal* selector.
cf-ui's bootstrap modal templates carry 8 modal-* class references each (jinja/bootstrap/Modal.jinja, cotton/_themes/bootstrap/modal.html).
v6 is expanding its JS surface, not shrinking it — combobox, datepicker, otp-input, chips, strength, drawer, menu, range are all new JS components on the branch.
So the bootstrap theme's markup breaks at v6 regardless of the JS decision, which is the useful part: the JS question gets re-asked for free the day the v6 markup rework happens. There is no need to pre-solve it now, and no need to rely on anyone remembering — the version bump is the natural trigger, and it is already a single pinned value in this repo.
Nothing in tests/ reads _DEFAULTS today. Guard it: a test that fails the moment this leaves the 5.x line, whose failure message is the checklist of what to reconsider. This is the pattern #17 established — pin the thing, and let the break on bump be the signal, rather than trusting a promise to monitor.
Whoever bumps that version is then forced to read the decision at the exact moment revisiting it is cheapest.
Acceptance criteria
Failing test first: a unit test asserting _DEFAULTS["bootstrap"] is on the 5. major line, with a failure message that names what to re-evaluate on v6 — .modal-* → .dialog-* class renames, showModal() vs. the role="dialog" decision recorded in Epic: axis + component hardening — close deferred review findings from #5, #6, #7 #19, whether v6's larger JS surface changes the CSS-only stance, and whether a driver/facade abstraction is warranted by then.
docs/bootstrap.md, following the shape of docs/daisyui.md, recording:
The decision, plainly: CSS only, Alpine drives behaviour, bootstrap.bundle.min.js is not emitted by cf_ui_body.
Why — Bootstrap sanctions framework-native replacement of its JS; a single behavioural contract (Alpine.store('cf').modal.open(id)) has to hold identically across all themes; Bootstrap ships no no-JS fallback at all, while cf-ui's js_off E2E tier is a stated guarantee.
Which of Bootstrap's 13 JS components cf-ui covers, and which it does not.
What a consumer should do to use an uncovered Bootstrap component, and the explicit warning that adding the bundle makes Bootstrap's JS a second state owner for the 5 components cf-ui already drives — data-bs-* attributes must not be added to cf-ui components.
The Bootstrap 6 note, stating what changes and that the pin test is the trigger.
The driver/facade option is recorded as a considered-and-deferred alternative, with the reason (an abstraction for a problem no consumer has reported, designed against an API that does not exist yet) — so it is not relitigated from scratch.
README's theme table or theme docs index links docs/bootstrap.md.
Delivery
Scoped to #29's branch — it is that PR's missing documentation, and splitting it into a follow-up would ship an undocumented decision in the meantime.
Part of #34.
Summary
#29 ships the
bootstraptheme as CSS only, with Alpine driving every interactive component andbootstrap.bundle.min.jsdeliberately absent. That is the right call, but it is currently an undocumented one: the reasoning exists only in a research artifact (claudedocs/research_bootstrap_js_convention_2026-07-29.md), which is a record of what was investigated, not a record of what was decided.Two concrete gaps follow from that, and this ticket closes both.
Consumers have no guidance. Bootstrap ships 13 JS-driven components. cf-ui wraps 5 of them. A consumer who wants a toast, an offcanvas, a carousel, or scrollspy has no documented answer for whether they may add Bootstrap's bundle themselves, and no warning about what happens if they do.
The decision has no expiry mechanism. It was made against Bootstrap 5 and is correct for Bootstrap 5. Bootstrap 6 changes the premises, and nothing in the repo will notice.
Why this needs a tripwire rather than a note
Verified directly against
v6-dev(secondary sources on this are unreliable — several claim v6 already moved to native<dialog>citing twbs#41751, which is closed and was never merged; twbs#39191 is closed NOT_PLANNED):modal.jsand_modal.scssare gone. They are replaced bydialog.ts/dialog-base.ts/_dialog.scss, built onHTMLDialogElement.showModal()._dialog.scssonv6-devdefines.dialog,.dialog-header,.dialog-body,.dialog-footer,.dialog-title,.dialog-scrollable,.dialog-fullscreen,.dialog-static,.dialog-nonmodal,.dialog-instant,.dialog-slide-up,.dialog-slide-down,.dialog-swap-in,.dialog-open. Not one.modal*selector.modal-*class references each (jinja/bootstrap/Modal.jinja,cotton/_themes/bootstrap/modal.html).combobox,datepicker,otp-input,chips,strength,drawer,menu,rangeare all new JS components on the branch.So the bootstrap theme's markup breaks at v6 regardless of the JS decision, which is the useful part: the JS question gets re-asked for free the day the v6 markup rework happens. There is no need to pre-solve it now, and no need to rely on anyone remembering — the version bump is the natural trigger, and it is already a single pinned value in this repo.
The pin is the hook
Nothing in
tests/reads_DEFAULTStoday. Guard it: a test that fails the moment this leaves the5.xline, whose failure message is the checklist of what to reconsider. This is the pattern #17 established — pin the thing, and let the break on bump be the signal, rather than trusting a promise to monitor.Whoever bumps that version is then forced to read the decision at the exact moment revisiting it is cheapest.
Acceptance criteria
_DEFAULTS["bootstrap"]is on the5.major line, with a failure message that names what to re-evaluate on v6 —.modal-*→.dialog-*class renames,showModal()vs. therole="dialog"decision recorded in Epic: axis + component hardening — close deferred review findings from #5, #6, #7 #19, whether v6's larger JS surface changes the CSS-only stance, and whether a driver/facade abstraction is warranted by then.docs/bootstrap.md, following the shape ofdocs/daisyui.md, recording:bootstrap.bundle.min.jsis not emitted bycf_ui_body.Alpine.store('cf').modal.open(id)) has to hold identically across all themes; Bootstrap ships no no-JS fallback at all, while cf-ui'sjs_offE2E tier is a stated guarantee.data-bs-*attributes must not be added to cf-ui components.docs/bootstrap.md.Delivery
Scoped to
#29's branch — it is that PR's missing documentation, and splitting it into a follow-up would ship an undocumented decision in the meantime.