TypeScript
Use first-party declarations for Boobstrap controllers, adapters, and generated design-token artifacts.
Declarations live with each package
Boobstrap v0.5 adds declarations to the dependency-free core JavaScript entry point and the Alpine adapter. The React and Vue adapters continue to publish declarations beside their headless hooks and composables. The generated JavaScript token artifact also includes a declaration file, so an application can inspect design tokens without maintaining a parallel interface.
| Import | Declaration coverage | Runtime boundary |
|---|---|---|
@boobstrap/boobstrap/js | All core controllers, initializers, options, and interaction-contract types. | Browser behavior; initialize only where DOM globals exist. |
@boobstrap/boobstrap/js/* | Typed direct-controller imports for tree-shaken application code. | Browser behavior for one controller. |
@boobstrap/alpine | Plugin, provider factories, provider state, and binding objects. | Alpine-owned browser behavior. |
@boobstrap/react | Hook options, results, prop getters, and adapter transition details. | SSR-safe headless hooks; React owns the component subtree. |
@boobstrap/vue | Composable options, computed results, refs, and prop getters. | SSR-safe headless composables; Vue owns the component subtree. |
@boobstrap/boobstrap/tokens | Named and default exports for tokens and mode overrides. | Generated ESM data; no DOM access. |
import "@boobstrap/boobstrap"; is a side-effect stylesheet import. It does not expose controllers or TypeScript declarations. Import runtime values and types from /js, a /js/* subpath, an adapter package, or /tokens.Core controllers in strict mode
The aggregate /js entry point is the convenient choice when an application uses several controllers or initBoobstrap. Controllers expose typed DOM elements, readonly state, lifecycle cleanup, and boolean transition results.
import {
Toast,
type TransitionOptions,
} from "@boobstrap/boobstrap/js";
const element = document.querySelector<HTMLElement>("#saved-toast");
if (!element) {
throw new Error("Missing #saved-toast");
}
const toast = Toast.getOrCreateInstance(element);
const transition: TransitionOptions = { reason: "profile-saved" };
if (!toast.visible) {
toast.show(transition);
}
// Remove listeners owned by this controller when its DOM subtree is discarded.
toast.destroy();
Use the aggregate initializer for progressive enhancement across a document or a smaller element boundary. Its result owns every controller it created and provides one teardown method.
import {
initBoobstrap,
type InitializerResult,
type Root,
} from "@boobstrap/boobstrap/js";
function enhance(root: Root): InitializerResult {
return initBoobstrap(root);
}
const accountArea = document.querySelector<HTMLElement>("#account-area");
const enhancement = enhance(accountArea ?? document);
// Call from your router, island, or application teardown path.
enhancement.destroy();
Prefer a subpath for one controller
Every core controller has a package-export subpath. A direct import keeps intent explicit and lets a bundler include only that controller and its internal dependencies.
import { Collapse } from "@boobstrap/boobstrap/js/collapse";
const panel = document.querySelector<HTMLElement>("#account-details");
if (panel) {
const details = Collapse.getOrCreateInstance(panel);
details.show();
}
Core declarations type controller methods and accepted transition options. They do not currently declare component-specific CustomEvent detail maps. Narrow and validate event.detail in application code instead of assuming an undocumented generic event type.
Alpine provider types
The Alpine package exports its plugin as the default value and exports each provider factory by name. Provider interfaces describe reactive state, methods, and the binding objects returned for x-bind. Register the plugin before Alpine.start(); do not initialize core controllers on the same subtree.
import boobstrap, {
accordion,
type AccordionProvider,
type AlpineLike,
} from "@boobstrap/alpine";
declare const Alpine: AlpineLike;
boobstrap(Alpine);
const typedFaq = (): AccordionProvider =>
accordion(["billing"], {
alwaysOpen: false,
onOpenIdsChange(ids) {
console.info("Open FAQ items", ids);
},
});
Alpine.data("typedFaq", typedFaq);
The same entry point declares button, collapse, combobox, dialog, dropdown, popover, tabs, toast, and tooltip. Binding objects intentionally use Record<string, unknown> because Alpine interprets their directive-shaped keys at runtime. Alpine itself does not currently publish declarations in its package, so the strict example types the narrow plugin boundary with Boobstrap's AlpineLike interface; keep application-specific Alpine registration in JavaScript or supply an application-owned ambient declaration when broader Alpine typing is required.
React hook types
React hooks infer controlled state, callback detail, and element-specific prop getters. Spread the returned props onto the semantic element they name. The adapter’s transition details include the literal discriminator adapter: "react".
import { useState } from "react";
import { useCollapse } from "@boobstrap/react";
export function AccountDetails() {
const [open, setOpen] = useState(false);
const details = useCollapse({
id: "account-details",
open,
onOpenChange(nextOpen, detail) {
detail.adapter satisfies "react";
setOpen(nextOpen);
},
});
return (
<>
<button className="bs-btn" {...details.getTriggerProps()}>
Account details
</button>
<div className="bs-collapse" {...details.getPanelProps()}>
Billing and profile settings
</div>
</>
);
}
Vue composable types
Vue composables use default* options for uncontrolled initial state. Pass a Vue ref to a controlled field such as open when the adapter should update that state reactively; a plain controlled value remains fixed until its owner supplies a new value. Public state is returned as ComputedRef, and prop getters return objects ready for v-bind. Adapter transition details use adapter: "vue".
<script setup lang="ts">
import { ref } from "vue";
import { useCollapse } from "@boobstrap/vue";
const open = ref(false);
const details = useCollapse({
id: "account-details",
open,
onOpenChange(nextOpen, detail) {
detail.adapter satisfies "vue";
open.value = nextOpen;
},
});
</script>
<template>
<button class="bs-btn" v-bind="details.getTriggerProps()">
Account details
</button>
<div class="bs-collapse" v-bind="details.getPanelProps()">
Billing and profile settings
</div>
</template>
Typed design-token artifacts
Import generated token data from @boobstrap/boobstrap/tokens. The module exports tokens, selector-keyed modes, and a default object containing both. Each leaf preserves a source-derived value and the originating custom-property name. Exact CSS var() aliases are normalized to Design Tokens Community Group references such as {brand.500}.
import artifact, {
modes,
tokens,
type DesignToken,
} from "@boobstrap/boobstrap/tokens";
function isDesignToken(value: DesignToken | string): value is DesignToken {
return typeof value !== "string";
}
const primary = tokens.color.primary;
if (isDesignToken(primary)) {
console.log(primary.$value);
console.log(primary.$extensions["org.boobstrap.css-variable"]);
}
for (const [selector, overrides] of Object.entries(modes)) {
console.log(selector, overrides["--bs-color-primary"]);
}
artifact.tokens === tokens; // true
Use @boobstrap/boobstrap/tokens.json when a build tool requires raw JSON instead of ESM. JSON import syntax and type inference depend on the host toolchain; the first-party DesignToken and TokenGroups declarations belong to the /tokens ESM entry.
Compiler configuration
Boobstrap packages are ESM and use package exports with a types condition. Use a modern module resolver so TypeScript follows the same entry points as the runtime. Core declarations reference browser interfaces including Document, Element, HTMLElement, and Event, so browser projects need the DOM library.
Use bundler resolution with Vite and similar tools that own final module transformation.
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM"]
}
}Use matching NodeNext module and resolution settings when TypeScript follows Node’s ESM rules directly.
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022", "DOM"]
}
}SSR and hydration boundaries
- CSS and token data: import them through the normal application or build-tool path. The ESM token artifact performs no DOM initialization.
- Core and Alpine behavior: call
initBoobstrap, create controllers, and start Alpine only in a browser lifecycle wheredocumentexists. Type availability does not make a DOM operation server-safe. - React and Vue adapters: use the headless hooks and composables during framework rendering. They avoid core-controller initialization; keep IDs and the initial controlled or uncontrolled state consistent between server output and hydration.
- Behavior ownership: never run core initialization over markup whose state is owned by Alpine, React, or Vue.
Troubleshooting
| Symptom | Correction |
|---|---|
“Cannot find module” for /js or /tokens | Confirm the installed package exposes the entry, then use Bundler, Node16, or NodeNext resolution. Legacy node resolution does not understand modern export conditions. |
Missing Document, HTMLElement, or Event | Add DOM to compilerOptions.lib for the browser-facing compilation unit. |
| The root package import has no named exports | That entry is intentionally the stylesheet. Import values and types from @boobstrap/boobstrap/js or a supported subpath. |
| React JSX props or Vue refs lose their types | Install the adapter’s framework peer and its normal TypeScript support, and import from @boobstrap/react or @boobstrap/vue rather than reaching into package source files. |
| Runtime behavior fires twice | This is an ownership problem, not a declaration problem. Remove core initialization from an Alpine-, React-, or Vue-owned subtree. |
| JSON token import is rejected | Use the typed /tokens ESM entry, or enable the JSON-module behavior required by your compiler and runtime. |
Use public subpaths
Import only from package exports documented on this page. Internal src/ and dist/ module layouts are not application APIs.
Keep strict mode on
Narrow queried elements and optional values instead of suppressing errors. The framework’s own declaration fixtures compile with strict checking.
Respect runtime ownership
Types describe a behavior layer; they do not coordinate multiple layers. Choose core, Alpine, React, or Vue once per component subtree.