Skip to content

Commit 3f8595f

Browse files
authored
feat(ilha): add .css and css`` for applying scoped styling to islands (#18)
* feat(ilha): add .css and css`` for applying scoped styling to islands * chore(ilha): adjust css readme
1 parent e9f6a30 commit 3f8595f

3 files changed

Lines changed: 593 additions & 8 deletions

File tree

packages/ilha/README.md

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ ilha
129129
.on("@click", ({ state }) => state.count(state.count() + 1)) // host click
130130
.on("button.inc@click", ({ state }) => state.count(state.count() + 1)) // child click
131131
.on("input@input:debounce", ({ state, event }) => {
132-
// with modifier
133132
state.query((event.target as HTMLInputElement).value);
134133
})
135134
.render(({ state }) => html`<div><button class="inc">+</button></div>`);
@@ -223,6 +222,63 @@ You can also bind to an external signal created with `context()`:
223222

224223
---
225224

225+
### `.css(strings, ...values)`
226+
227+
Attaches scoped styles to the island. Accepts a tagged template literal or a plain string. The CSS is automatically wrapped in a `@scope` rule bounded to the island host, so styles are contained within the island and do not leak into child islands.
228+
229+
```ts
230+
import { css } from "ilha";
231+
232+
const Card = ilha.state("active", false).css`
233+
.title { font-weight: 700; }
234+
button { background: teal; color: white; }
235+
`.render(
236+
({ state }) => html`
237+
<div>
238+
<p class="title">Hello</p>
239+
<button>Toggle</button>
240+
</div>
241+
`,
242+
);
243+
```
244+
245+
Interpolations are supported:
246+
247+
```ts
248+
const accent = "teal";
249+
250+
ilha.css`button { background: ${accent}; }`.render(() => `<button>Go</button>`);
251+
```
252+
253+
You can also pass a plain string (e.g. from an external `.css` file):
254+
255+
```ts
256+
import styles from "./card.css?raw";
257+
258+
ilha.css(styles).render(() => `<div class="card">…</div>`);
259+
```
260+
261+
**SSR output** — a `<style data-ilha-css>` tag is prepended as the first child of the island's rendered HTML:
262+
263+
```html
264+
<style data-ilha-css>
265+
@scope (:scope) to ([data-ilha]) {
266+
.title {
267+
font-weight: 700;
268+
}
269+
}
270+
</style>
271+
<div>…</div>
272+
```
273+
274+
**Client mount** — the style element is injected once as the first child of the host and preserved across re-renders (morph never replaces it). During hydration, the SSR-emitted `<style>` node is reused and not duplicated.
275+
276+
**`.hydratable()` integration** — the style tag is included inside the `data-ilha` wrapper regardless of the `snapshot` option.
277+
278+
> **Note:** Calling `.css()` more than once on the same builder chain is not supported. In dev mode a warning is logged and only the last stylesheet is used. Compose all your styles into a single `.css()` call.
279+
280+
---
281+
226282
### `.slot(name, island)`
227283

228284
Embeds a child island as a named slot. The child island is mounted and managed independently. During SSR the slot renders the child's HTML inline; during client mount the child island is activated for interactivity.
@@ -430,6 +486,41 @@ raw("<strong>bold</strong>"); // → passes through unescaped
430486

431487
---
432488

489+
### `css\`\`` tagged template
490+
491+
A passthrough tagged template for CSS strings. Functionally identical to a plain template literal — no runtime transformation occurs. Its purpose is purely to enable editor tooling (LSP syntax highlighting, Prettier formatting) to recognise the contents as CSS.
492+
493+
```ts
494+
import { css } from "ilha";
495+
496+
const styles = css`
497+
button {
498+
background: teal;
499+
color: white;
500+
}
501+
.label {
502+
font-weight: 700;
503+
}
504+
`;
505+
506+
ilha.css(styles).render(() => `<button class="label">Go</button>`);
507+
```
508+
509+
Interpolations work as normal string concatenation:
510+
511+
```ts
512+
const accent = "coral";
513+
const styles = css`
514+
button {
515+
background: ${accent};
516+
}
517+
`;
518+
```
519+
520+
> **Note:** `css` (the named export) is the plain passthrough tag for tooling. `ilha.css` is the builder chain method that attaches styles to an island. They are intentionally separate.
521+
522+
---
523+
433524
### `type(coerce?)`
434525

435526
Creates a lightweight Standard Schema validator for use with `.input()` — useful when you don't want a full validation library.

0 commit comments

Comments
 (0)