IslandCss
Bastion.IslandCss — scoped CSS for WASM islands.
Transforms flat CSS into attribute-scoped CSS so island styles don't leak to the rest of the page or to other islands.
Scoping mechanism:
- Generate a unique data attribute for each island module.
e.g. "SearchBar" → data-b-search-bar
2. Prefix every CSS selector with the attribute selector.
e.g. ".btn { … }" → "[data-b-search-bar] .btn { … }"
3. Islands.wrap/4 adds the data attribute to the island's root element,
so the scoped selectors match.@ rules (media queries, keyframes, etc.) and special selectors (:root, html, body, *) are passed through unmodified.
Usage:
let css = ".counter { display: flex; }\n.count { font-size: 2rem; }"
let scoped = IslandCss.scope_css(css, "Counter")
-- "[data-b-counter] .counter { display: flex; }\n[data-b-counter] .count { font-size: 2rem; }"
let attr = IslandCss.attr_name("SearchBar") -- "data-b-search-bar"Island declaration (Bastion convention):
mod MyApp.Islands.Counter do
import Bastion.Island
fn styles() : String do
".counter { display: flex; gap: 8px; }" ++
".count { font-size: 2rem; font-weight: bold; }"
end
-- ... init, update, view ...
endFunctions
Return the HTML attribute name (without brackets) for an island.
This is the attribute added to the island's root element by Islands.wrap.
Example:
IslandCss.attr_name("Counter") -- "data-b-counter"
IslandCss.attr_name("SearchBar") -- "data-b-search-bar"Return the CSS attribute selector used to scope an island's styles.
Example:
IslandCss.attribute_for("SearchBar") -- "[data-b-search-bar]"
IslandCss.attribute_for("Counter") -- "[data-b-counter]"
IslandCss.attribute_for("ChatWidget") -- "[data-b-chat-widget]"Scope all CSS selectors in css to the island named island_name.
Each CSS selector is prefixed with `[data-b-{kebab-name}]` so rules only
apply inside that island's DOM subtree. Comma-separated selector lists are
each prefixed individually. @ rules and global selectors (:root, html,
body, *) are passed through without modification.
Example:
IslandCss.scope_css(".btn { color: red; }", "MyButton")
-- "[data-b-my-button] .btn { color: red; }"
IslandCss.scope_css(".a, .b { margin: 0; }", "Card")
-- "[data-b-card] .a,\n[data-b-card] .b { margin: 0; }"Convert a PascalCase or CamelCase identifier to kebab-case.
Each uppercase letter that is not the first character is preceded by a "-".
Digits and already-lowercase characters are preserved.
Example:
IslandCss.to_kebab("SearchBar") -- "search-bar"
IslandCss.to_kebab("MyIsland") -- "my-island"
IslandCss.to_kebab("Counter") -- "counter"
IslandCss.to_kebab("ChatWidget") -- "chat-widget"
IslandCss.to_kebab("UserProfileCard") -- "user-profile-card"