diff --git a/docs/src/content/docs/api/cem-plugin.md b/docs/src/content/docs/api/cem-plugin.md
index 5fab5cd..422e98f 100644
--- a/docs/src/content/docs/api/cem-plugin.md
+++ b/docs/src/content/docs/api/cem-plugin.md
@@ -44,7 +44,7 @@ initializer and, for every key, records in the manifest:
- the **member** under its camelCase name
- the **attribute** under its kebab-case name, linked to that member
-- the **type** inferred from the default value — `boolean`, `number`, `object`
+- the **type** inferred from the default value: `boolean`, `number`, `object`
or `string`
- the **default value** as written
diff --git a/docs/src/content/docs/api/html.md b/docs/src/content/docs/api/html.md
index 77412b1..ff352fb 100644
--- a/docs/src/content/docs/api/html.md
+++ b/docs/src/content/docs/api/html.md
@@ -48,7 +48,7 @@ html`
hi
`
Because the tree is a plain object it is comparable and serializable, which is
what lets `render()` diff one render against the next.
-`` html`` `` returns `undefined` — this is the idiomatic way for a component to
+`` html`` `` returns `undefined`. This is the idiomatic way for a component to
render nothing, and it empties the rendered subtree rather than leaving the
previous render on screen.
@@ -60,7 +60,7 @@ Each entry in `props` is applied by [`applyProp`](/api/utils/#applypropel-prop-v
in this order:
1. a `style` object is applied rule by rule
-2. a name the element owns as a **DOM property** is assigned to that property —
+2. a name the element owns as a **DOM property** is assigned to that property,
so event handlers (`onclick=${fn}`) and non-string values keep their type
3. a boolean value with no matching DOM property is toggled as an HTML boolean
attribute
diff --git a/docs/src/content/docs/api/utils.md b/docs/src/content/docs/api/utils.md
index e4f7c28..009a222 100644
--- a/docs/src/content/docs/api/utils.md
+++ b/docs/src/content/docs/api/utils.md
@@ -63,7 +63,7 @@ everything else pass through unchanged.
### `deserialize(value, type)`
-Parses an attribute string back into a value of the given declared type — the
+Parses an attribute string back into a value of the given declared type, the
inverse of `serialize()`.
| Parameter | Type | |
@@ -72,7 +72,7 @@ inverse of `serialize()`.
| `type` | `string` | `'boolean'`, `'number'`, `'object'`, `'undefined'` or `'string'` |
| **returns** | `any` | the parsed value |
-`'boolean'` always returns `true` — strict HTML boolean-attribute semantics,
+`'boolean'` always returns `true`: strict HTML boolean-attribute semantics,
where any present value is true. Absence is handled by the caller and never
reaches here. `'number'`, `'object'` and `'undefined'` use `JSON.parse` and
throw on malformed input; strings pass through.
diff --git a/docs/src/content/docs/api/web-component.md b/docs/src/content/docs/api/web-component.md
index 6d27b57..bdfd28b 100644
--- a/docs/src/content/docs/api/web-component.md
+++ b/docs/src/content/docs/api/web-component.md
@@ -1,7 +1,7 @@
---
title: WebComponent
slug: api/web-component
-description: The WebComponent base class — static configuration, instance members, lifecycle hooks and attribute converters.
+description: 'The WebComponent base class: static configuration, instance members, lifecycle hooks and attribute converters.'
---
The base class every component extends. Import from the package root or its own
@@ -56,8 +56,8 @@ reported with `console.warn`:
| Default | Warning |
| ------------------ | --------------------------------------------------- |
-| a function or symbol | not reflectable — use handlers or refs instead |
-| `true` | boolean defaults should be `false` — invert the name |
+| a function or symbol | not reflectable: use handlers or refs instead |
+| `true` | boolean defaults should be `false`: invert the name |
A `true` boolean default is discouraged because HTML has no true-by-default
boolean attribute: absence would have to mean both "false" and "default". Name
@@ -80,7 +80,7 @@ per-component rules after it. Strings are compiled to a `CSSStyleSheet` once;
existing `CSSStyleSheet` instances are adopted as-is and can be shared across
components.
-Adoption happens **once per instance**, when the element is constructed — not
+Adoption happens **once per instance**, when the element is constructed, not
per render.
Requires [`shadowRootInit`](#static-shadowrootinit). Without a shadow root
@@ -92,7 +92,7 @@ See it live: [Constructable styles demo ↗](https://demo.webcomponent.io/exampl
### `static shadowRootInit`
A [`ShadowRootInit`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options)
-object. Its presence is what opts the component into shadow DOM — the shadow
+object. Its presence is what opts the component into shadow DOM. The shadow
root is attached during construction and becomes the render target.
```js
@@ -143,9 +143,9 @@ Assigning the value it already holds does nothing.
Read-only getter returning what the component renders. Two kinds are supported:
-- an [`html`](/api/html/) tagged template — a vnode tree, reconciled in place
+- an [`html`](/api/html/) tagged template: a vnode tree, reconciled in place
on re-render
-- a **string** — assigned to the render target's `innerHTML`
+- a **string**: assigned to the render target's `innerHTML`
Both render into the same target: the shadow root when `shadowRootInit` is set,
the element itself otherwise. Returning `` html`` `` (which is `undefined`) or
@@ -165,7 +165,7 @@ Renders `template` into the render target. Called automatically on connect and
on every prop or attribute change; you rarely call it yourself.
For a vnode template, the new tree is compared against the previous one and
-re-render **patches the existing DOM in place** — see
+re-render **patches the existing DOM in place**. See
[Template vs Render](/template-vs-render/) for what that preserves and the
non-keyed matching caveat.
@@ -202,7 +202,7 @@ Override these to control how one prop crosses the prop/attribute boundary, and
call `super` for the props you do not handle.
The default conversion round-trips values through JSON. Types JSON cannot
-restore — `Date`, `Map`, `Set`, `URL`, class instances — need overridden
+restore (`Date`, `Map`, `Set`, `URL`, class instances) need overridden
converters to live on `static props`; see
[Custom attribute conversion](/prop-access/#custom-attribute-conversion) for
worked examples, including the non-serializable cases.
@@ -229,7 +229,7 @@ toAttribute(name, value) {
### `fromAttribute(name, value)`
-Converts an attribute value into the prop value it represents — the inverse of
+Converts an attribute value into the prop value it represents, the inverse of
`toAttribute()`.
| Parameter | Type | Description |
@@ -256,7 +256,7 @@ Boolean props follow the HTML convention in both directions: **presence means
| `true` | present, empty value | `''` |
| `false` | absent | `null` |
-Any present value reads as `true` — including the literal `flag="false"`, just
+Any present value reads as `true`, including the literal `flag="false"`, just
as native `disabled="false"` is still disabled. Removing the attribute always
yields `false`, never the declared default.
@@ -265,7 +265,7 @@ Use `toggleAttribute(name, bool)` to set them. Writing
when it sees a boolean attribute written as `"true"` or `"false"` so the
inversion cannot fail silently.
-Attributes whose `"false"` is meaningful — `aria-*`, `contenteditable` — should
+Attributes whose `"false"` is meaningful (`aria-*`, `contenteditable`) should
be declared as **string** props.
See it live: [Boolean props demo ↗](https://demo.webcomponent.io/examples/boolean-props/)
diff --git a/docs/src/content/docs/guides/cem-plugin.mdx b/docs/src/content/docs/guides/cem-plugin.mdx
index d4b2fba..5027ba9 100644
--- a/docs/src/content/docs/guides/cem-plugin.mdx
+++ b/docs/src/content/docs/guides/cem-plugin.mdx
@@ -6,7 +6,7 @@ slug: cem-plugin
import { Aside } from '@astrojs/starlight/components'
A CEM (`custom-elements.json`) is a standard description of the elements a
-package defines — their tags, attributes, properties, events and slots — so
+package defines (their tags, attributes, properties, events and slots) so
tooling can read your components without executing them. Read more at
[custom-elements-manifest.open-wc.org](https://custom-elements-manifest.open-wc.org/)
for the analyzer and its plugin API, or the [schema and
@@ -20,7 +20,7 @@ We provide a CEM Analyzer Plugin `web-component-base/cem-plugin` which allows us
set up: a starter component, this plugin configured in
`custom-elements-manifest.config.mjs`, an `analyze` script, and the
`customElements` field set in `package.json`. The rest of this guide is the
- manual path — use it to add the manifest to an existing project, or to
+ manual path: use it to add the manifest to an existing project, or to
understand what the scaffold configured.
@@ -53,10 +53,10 @@ npx cem analyze
Run it **from the directory holding your config and a `package.json`**. Two
things make it appear to do nothing, neither of which prints an error:
-- **No config found** — it falls back to a default glob of
+- **No config found**: it falls back to a default glob of
`**/*.{js,ts,tsx}`, which does _not_ exclude `node_modules`. In a real
project that is tens of thousands of files through the TypeScript parser.
-- **No `package.json` in the directory** — it hangs outright.
+- **No `package.json` in the directory**: it hangs outright.
With globs scoped to your source it should finish in well under a second.
@@ -92,10 +92,10 @@ customElements.define('cozy-button', CozyButton)
Two details worth knowing:
-- **Types come from the default literal** — `true`/`false` → `boolean`, numeric → `number`, object/array → `object`, everything else → `string`.
+- **Types come from the default literal**: `true`/`false` → `boolean`, numeric → `number`, object/array → `object`, everything else → `string`.
- **Attribute names come from wcb's own `getKebabCase`**, the same function `observedAttributes` uses, so manifest names can't drift from what the component actually observes.
-The defaults may be written inline or hoisted into a module-level `const` — the latter is required by the [typed props](/prop-access/#typed-props-in-typescript) pattern, and the plugin resolves it either way.
+The defaults may be written inline or hoisted into a module-level `const`. The latter is required by the [typed props](/prop-access/#typed-props-in-typescript) pattern, and the plugin resolves it either way.
## Storybook
@@ -113,7 +113,7 @@ setCustomElementsManifest(manifest)
export default { tags: ['autodocs'] }
```
-Bind a story to the tag name and Storybook infers the rest — a text field for `variant`, a toggle for `disabled`, a number input for `maxCount`:
+Bind a story to the tag name and Storybook infers the rest, giving a text field for `variant`, a toggle for `disabled`, a number input for `maxCount`:
```js
// cozy-button.stories.js
@@ -133,19 +133,19 @@ export const Default = { args: { variant: 'primary', disabled: false } }
-This repo runs exactly this setup against the demo components in `storybook/` — see it there for a working reference.
+This repo runs exactly this setup against the demo components in `storybook/`. See it there for a working reference.
## Code editors
-Once `custom-elements.json` exists, editors can offer tag-name and attribute autocomplete for your components — driven by the same `static props` the plugin reads, so the hints can't drift from the code.
+Once `custom-elements.json` exists, editors can offer tag-name and attribute autocomplete for your components, driven by the same `static props` the plugin reads, so the hints can't drift from the code.
First, point tooling at the manifest from your `package.json`. This is the field every option here uses to discover it:
@@ -156,7 +156,7 @@ First, point tooling at the manifest from your `package.json`. This is the field
}
```
-### Route 1 — native VS Code, no extension
+### Route 1: native VS Code, no extension
VS Code's built-in HTML language service reads its own [custom data](https://github.com/microsoft/vscode-custom-data) format. A second analyzer plugin converts the manifest into it, so both files come out of one `cem analyze` run:
@@ -187,25 +187,25 @@ export default {
`html.customData` paths resolve from the **workspace root**, not from the
settings file. If you run `cem analyze` in a subfolder, either point at
`./that-folder/vscode.html-custom-data.json` or give the generator its own
- `outdir` — `generateCustomData({ outdir: '..' })` — so the file lands where
+ `outdir`, `generateCustomData({ outdir: '..' })`, so the file lands where
the setting expects it.
Restart VS Code and `
This corner of the ecosystem moves quickly and every option is pre-1.0. Route
- 1 is the conservative choice — it is plain VS Code configuration with no
- extension to go stale — at the cost of not covering tagged templates.
+ 1 is the conservative choice (it is plain VS Code configuration with no
+ extension to go stale) at the cost of not covering tagged templates.
diff --git a/docs/src/content/docs/guides/comparison.md b/docs/src/content/docs/guides/comparison.md
index 312a8b3..1f201b1 100644
--- a/docs/src/content/docs/guides/comparison.md
+++ b/docs/src/content/docs/guides/comparison.md
@@ -17,13 +17,13 @@ Numbers below are **measured** from the same minimal counter component (one reac
| `@elenajs/core` | 1.0.0 | 9.1 kB | 3.7 kB | 3.3 kB |
| `lit` | 3.3.3 | 15.3 kB | 5.9 kB | 5.3 kB |
| `@microsoft/fast-element` | 3.0.1 | 44.9 kB | 13.7 kB | 12.2 kB |
-| vanilla `HTMLElement` | — | — | — | ~0.2 kB |
+| vanilla `HTMLElement` | - | - | - | ~0.2 kB |
For scale: even after all of the v5.2–v6.1 work, the WCB counter is **~21% smaller than Elena, ~51% smaller than Lit, and ~79% smaller than FAST**.
## Feature comparison
-What each library gives you beyond extending directly from `HTMLElement` — the boilerplate you no longer write by hand:
+What each library gives you beyond extending directly from `HTMLElement`, the boilerplate you no longer write by hand:
| Capability | WCB 6.1 | Lit 3.3 | Elena 1.0 | FAST 3.0 |
| -------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------- | --------------------------------------- | ------------------------------------------------ |
@@ -42,11 +42,11 @@ What each library gives you beyond extending directly from `HTMLElement` — the
| Backing / ecosystem | solo maintainer, small surface | Google, huge ecosystem | new (2026), design-system focus | Microsoft, powers Fluent UI |
:::note[Why 11ty WebC isn't here]
-WebC is a compile-time tool: it resolves components during an Eleventy build and ships plain HTML with no client runtime. Every row above is about what a library does _in the browser at runtime_, so a side-by-side comparison would be measuring two different things. If your components are static at build time, WebC solves a different problem — and solves it well.
+WebC is a compile-time tool: it resolves components during an Eleventy build and ships plain HTML with no client runtime. Every row above is about what a library does _in the browser at runtime_, so a side-by-side comparison would be measuring two different things. If your components are static at build time, WebC solves a different problem, and solves it well.
:::
-For what these numbers and capabilities add up to — and when they don't — see [Why would anyone use WCB?](/why/).
+For what these numbers and capabilities add up to (and when they don't) see [Why would anyone use WCB?](/why/).
---
-_WCB re-measured 2026-07-20 at v6.1.0; the other libraries measured 2026-07-19, with esbuild, Node zlib (gzip −9, brotli q11), at the pinned versions above. Methodology: identical counter component per library, bundled per library, compressed. Re-run them yourself — the benchmark is trivially reproducible with the versions pinned above._
+_WCB re-measured 2026-07-20 at v6.1.0; the other libraries measured 2026-07-19, with esbuild, Node zlib (gzip −9, brotli q11), at the pinned versions above. Methodology: identical counter component per library, bundled per library, compressed. Re-run them yourself. The benchmark is trivially reproducible with the versions pinned above._
diff --git a/docs/src/content/docs/guides/examples.md b/docs/src/content/docs/guides/examples.md
index 28ee48c..502ee1f 100644
--- a/docs/src/content/docs/guides/examples.md
+++ b/docs/src/content/docs/guides/examples.md
@@ -6,7 +6,7 @@ slug: examples
## Live demo gallery
Every example below runs as a standalone page at
-[demo.webcomponent.io ↗](https://demo.webcomponent.io/) — a live gallery with
+[demo.webcomponent.io ↗](https://demo.webcomponent.io/), a live gallery with
the source alongside each demo.
| Demo | Shows |
diff --git a/docs/src/content/docs/guides/getting-started.mdx b/docs/src/content/docs/guides/getting-started.mdx
index e5a1e25..cb5b8e8 100644
--- a/docs/src/content/docs/guides/getting-started.mdx
+++ b/docs/src/content/docs/guides/getting-started.mdx
@@ -20,9 +20,9 @@ npm create wcb@latest
It sets up a publishable custom element, ready to develop:
-- **A starter component** in TypeScript using the [`static props`](/prop-access/) convention — the class, tag, and file names are derived from your project name (`my-button` → `MyButton` / ``), so there are no rename-me TODOs
-- **A Vite setup** — a dev server with a demo page, plus a library build (`npm run build:lib`) that emits ESM + UMD bundles and `.d.ts` types, with `web-component-base` as a peerDependency
-- **`custom-elements.json` generation** — the CEM analyzer configured with wcb's plugin, an `npm run analyze` script, the `customElements` field in `package.json`, and a `prepack` hook so the manifest ships inside the published package
+- **A starter component** in TypeScript using the [`static props`](/prop-access/) convention. The class, tag, and file names are derived from your project name (`my-button` → `MyButton` / ``), so there are no rename-me TODOs
+- **A Vite setup**: a dev server with a demo page, plus a library build (`npm run build:lib`) that emits ESM + UMD bundles and `.d.ts` types, with `web-component-base` as a peerDependency
+- **`custom-elements.json` generation**: the CEM analyzer configured with wcb's plugin, an `npm run analyze` script, the `customElements` field in `package.json`, and a `prepack` hook so the manifest ships inside the published package
Run the demo page to see the starter component render:
@@ -32,7 +32,7 @@ npm install
npm run dev
```
-Vite prints a local URL — open it to see `` rendered on the demo page. Edit `src/MyButton.ts` and the page updates as you save.
+Vite prints a local URL. Open it to see `` rendered on the demo page. Edit `src/MyButton.ts` and the page updates as you save.
See the [CEM Analyzer Plugin guide](/cem-plugin/) for more details on the manifest and how to use it with Storybook and code editors.
@@ -57,7 +57,7 @@ import { WebComponent } from 'web-component-base'
Continue with [Usage](/usage) to define your first component.
:::tip[Prefer a CDN?]
-You can also import wcb straight from a CDN in vanilla JS or HTML files — see the [CodePen examples](/examples/#codepen-examples) for working setups.
+You can also import wcb straight from a CDN in vanilla JS or HTML files. See the [CodePen examples](/examples/#codepen-examples) for working setups.
:::
## Getting help
diff --git a/docs/src/content/docs/guides/life-cycle-hooks.md b/docs/src/content/docs/guides/life-cycle-hooks.md
index a7c97ee..40013ab 100644
--- a/docs/src/content/docs/guides/life-cycle-hooks.md
+++ b/docs/src/content/docs/guides/life-cycle-hooks.md
@@ -77,9 +77,9 @@ class ClickableText extends WebComponent {
- Triggered when an attribute value changed
- The `changes` object cleanly separates the **property** from the **attribute**:
- - `property` — the **camelCase** prop key, matching how you access `props` (e.g. `myName`)
- - `attribute` — the **kebab-case** attribute name that changed (e.g. `my-name`)
- - `previousValue` / `currentValue` — the values before and after the change
+ - `property`: the **camelCase** prop key, matching how you access `props` (e.g. `myName`)
+ - `attribute`: the **kebab-case** attribute name that changed (e.g. `my-name`)
+ - `previousValue` / `currentValue`: the values before and after the change
Use `property` to read the value straight off `props` (`this.props[property]`); use `attribute` when you need the raw attribute name.
@@ -117,7 +117,7 @@ If you previously read `changes.property` for the attribute name, switch to `cha
## Upgrade ordering & the buffering guarantee
-Per the Custom Elements spec, when an element is upgraded with attributes already present in the markup (e.g. ``), the browser fires `attributeChangedCallback` **before** `connectedCallback`. Taken literally, that means `render()` and `onChanges()` could run before `onInit()` — so any setup you do in `onInit` (event wiring, reading external state) would not have happened yet on that first render. Test environments like happy-dom/jsdom don't reproduce this ordering, so components can pass in tests and then misbehave in a real browser.
+Per the Custom Elements spec, when an element is upgraded with attributes already present in the markup (e.g. ``), the browser fires `attributeChangedCallback` **before** `connectedCallback`. Taken literally, that means `render()` and `onChanges()` could run before `onInit()`, so any setup you do in `onInit` (event wiring, reading external state) would not have happened yet on that first render. Test environments like happy-dom/jsdom don't reproduce this ordering, so components can pass in tests and then misbehave in a real browser.
`WebComponent` removes this footgun. Attribute changes that arrive **before** the element is connected are buffered:
@@ -126,8 +126,8 @@ Per the Custom Elements spec, when an element is upgraded with attributes alread
On connect, the order is always:
-1. `onInit()` — `this.props` already reflects any authored attributes
-2. a single `render()` — reflects all buffered props in one pass
+1. `onInit()`: `this.props` already reflects any authored attributes
+2. a single `render()`: reflects all buffered props in one pass
3. `afterViewInit()`
-**`onChanges()` never fires before `onInit()`.** Pre-connect attribute changes are **not** replayed through `onChanges()` — the first `render()` already reflects them, so `onChanges()` is reserved for genuine post-connect changes. After the element is connected, attribute changes behave normally: each one triggers `render()` and `onChanges()` immediately.
+**`onChanges()` never fires before `onInit()`.** Pre-connect attribute changes are **not** replayed through `onChanges()`. The first `render()` already reflects them, so `onChanges()` is reserved for genuine post-connect changes. After the element is connected, attribute changes behave normally: each one triggers `render()` and `onChanges()` immediately.
diff --git a/docs/src/content/docs/guides/prop-access.mdx b/docs/src/content/docs/guides/prop-access.mdx
index 660eee8..1541ce7 100644
--- a/docs/src/content/docs/guides/prop-access.mdx
+++ b/docs/src/content/docs/guides/prop-access.mdx
@@ -65,7 +65,7 @@ class FlagBox extends WebComponent {
```
-Reflection works the same way in reverse — `true` sets the bare attribute,
+Reflection works the same way in reverse. `true` sets the bare attribute,
`false` removes it entirely:
```js
@@ -88,7 +88,7 @@ el.toggleAttribute('flag', true) // prop syncs, component re-renders
Enumerated attributes like `contenteditable` and `aria-*` attributes are the
-exception — they are genuine strings where `"false"` is meaningful, so declare
+exception. They are genuine strings where `"false"` is meaningful, so declare
them as **string** props rather than booleans. Strings serialize literally and
are never removed, so they need nothing special at runtime; in TypeScript you
can narrow them to the values you accept:
@@ -109,7 +109,7 @@ const props = { ariaChecked: 'false' as 'true' | 'false' }
HTML has no true-default boolean attribute: absence has to mean both "false"
and "default", which only works when they coincide. Model an on-by-default
flag with an inverted name (`disabled`, not `enabled`). wcb warns once per
- class on a `true` default — and for such a prop, removing the attribute lands
+ class on a `true` default, and for such a prop, removing the attribute lands
on `false`, not back on the declared default.
@@ -118,8 +118,8 @@ const props = { ariaChecked: 'false' as 'true' | 'false' }
See it live: [Custom attribute converters demo ↗](https://demo.webcomponent.io/examples/attribute-converters/)
The rules above cover the common cases. When a prop needs its own
-serialization — a `Date`, a delimited list, an enumerated attribute where
-`"false"` is meaningful — override `toAttribute` and `fromAttribute`, and
+serialization (a `Date`, a delimited list, an enumerated attribute where
+`"false"` is meaningful) override `toAttribute` and `fromAttribute`, and
delegate everything else to `super`:
```js
@@ -144,7 +144,7 @@ class EventCard extends WebComponent {
```
Both take the **camelCase prop key**, matching your `static props` declaration
-and `onChanges`'s `property` — not the kebab-case attribute name.
+and `onChanges`'s `property`, not the kebab-case attribute name.
`toAttribute` returning **`null` removes the attribute**. That is exactly how a
`false` boolean becomes an absent attribute, and it is available to any prop:
@@ -158,7 +158,7 @@ toAttribute(name, value) {