diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index b179d9b..d7bbfdf 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -32,6 +32,7 @@ export default defineConfig({ items: [ { label: 'Getting Started', slug: 'guides/getting-started' }, { label: 'Bring Your Own Renderer', slug: 'guides/renderers' }, + { label: 'Styling', slug: 'guides/styling' }, { label: 'Attributes', slug: 'guides/attributes' }, { label: 'Properties', slug: 'guides/properties' }, { label: 'API', slug: 'guides/api' }, diff --git a/docs/src/content/docs/guides/index.mdx b/docs/src/content/docs/guides/index.mdx index c2ef52c..a91476f 100644 --- a/docs/src/content/docs/guides/index.mdx +++ b/docs/src/content/docs/guides/index.mdx @@ -12,22 +12,25 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; Use your preferred templating library — Lit, htm, or a custom renderer. - + + Style components with adopted style sheets, css helpers, and CSS modules. + + React to attribute changes on your custom elements. - + Manage reactive properties with type-safe accessors. - + Explore the full pion API reference. - + Emit and listen to custom DOM events from hooks. - + Compose lightweight components without custom element registration. - + Get full type inference for hooks, props, and events. diff --git a/docs/src/content/docs/guides/styling.md b/docs/src/content/docs/guides/styling.md new file mode 100644 index 0000000..9ce49ab --- /dev/null +++ b/docs/src/content/docs/guides/styling.md @@ -0,0 +1,147 @@ +--- +title: Styling +description: Style pion components with constructable stylesheets — the css helper, the styleSheets option, native CSS modules, and light DOM alternatives. +--- + +pion components render into a [shadow root](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) by default, so their styles are naturally isolated from the page. pion uses [constructable stylesheets](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#applying_styles_inside_the_shadow_dom): styles are declared with the `css` helper and adopted on the component's shadow root via the `styleSheets` option — constructed once, shared by every instance of the component. + +## Component styles + +The recommended pattern is a dedicated style file next to your component. The file exports a `css` template; the component imports it and passes it to `component()`: + +```js +// my-app.style.ts +import { css } from '@pionjs/pion'; + +export const style = css` + :host { + display: block; + padding: 1rem; + } + .content { + color: var(--my-app-color, currentColor); + } +`; +``` + +```js +// my-app.ts +import { component, html } from '@pionjs/pion'; +import { style } from './my-app.style'; + +function App() { + return html`
Hello!
`; +} + +customElements.define('my-app', component(App, { styleSheets: [style] })); +``` + +This keeps the component function focused on behavior, and makes styles reusable across components. + +`styleSheets` accepts a mix of strings and `CSSStyleSheet` instances — strings are converted to stylesheets automatically. + +### The `css` tagged template + +`css` is a tagged template that returns a plain string. It supports interpolation, which makes it easy to compose styles or build themes from parts: + +```js +import { css } from '@pionjs/pion'; + +const base = css` + :host { display: block; } +`; + +const theme = css` + :host { --my-app-color: rebeccapurple; } +`; + +export const style = css` + ${base} + ${theme} + .content { color: var(--my-app-color); } +`; +``` + +:::caution +Since `css` returns a string, interpolating a value like `0` or `false` results in an empty string (falsy values are dropped). Coerce interpolated values to strings when in doubt. +::: + +### Attaching styles to the renderer function + +Instead of the options object, you can set `styleSheets` as a static property on the component function itself: + +```js +function App() { + return html`
Hello!
`; +} + +App.styleSheets = [style]; + +customElements.define('my-app', component(App)); +``` + +If both are provided, the renderer's `styleSheets` take precedence over the ones passed to `component()`. + +### The `sheet()` helper + +If you need a `CSSStyleSheet` directly (for example to adopt it on a document or another shadow root), use the `sheet` helper: + +```js +import { sheet } from '@pionjs/pion'; + +const styles = sheet(':host { display: block; }', '.content { color: red; }'); + +document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles]; +``` + +## Native CSS modules + +With the [CSS modules](https://developer.chrome.com/docs/css-ui/css-modules) import attribute (`with { type: 'css' }`), the browser hands you a ready-made `CSSStyleSheet`. Since `styleSheets` accepts existing instances, they work out of the box: + +```js +import { component, html } from '@pionjs/pion'; +import styles from './my-app.css' with { type: 'css' }; + +function App() { + return html`
Hello!
`; +} + +customElements.define('my-app', component(App, { styleSheets: [styles] })); +``` + +Bundler notes: + +- **Vite** and **Rollup** support `with { type: 'css' }` — the imported value is a constructed stylesheet that adopts cleanly into shadow roots. +- This syntax requires a modern browser or a bundler that transforms it; it does **not** work from a plain `