# Create access tier
Source: https://docs.beyondwords.io/api-reference/access-tiers/create
post /projects/{project_id}/player_settings/access_tiers
Creates an access tier for a project to control player access to content.
# List access tiers
Source: https://docs.beyondwords.io/api-reference/access-tiers/list
get /projects/{project_id}/player_settings/access_tiers
Lists access tiers configured for a project.
# Get access tier
Source: https://docs.beyondwords.io/api-reference/access-tiers/show
get /projects/{project_id}/player_settings/access_tiers/{id}
Returns a single access tier by ID.
# Update access tier
Source: https://docs.beyondwords.io/api-reference/access-tiers/update
put /projects/{project_id}/player_settings/access_tiers/{id}
Updates an access tier for a project.
# Create ad
Source: https://docs.beyondwords.io/api-reference/ads/create
post /projects/{project_id}/ads
Creates an ad in a project.
# Delete ad
Source: https://docs.beyondwords.io/api-reference/ads/delete
delete /projects/{project_id}/ads/{id}
Deletes an ad from a project.
# List ads
Source: https://docs.beyondwords.io/api-reference/ads/list
get /projects/{project_id}/ads
Lists ads configured for a project.
# Get ad
Source: https://docs.beyondwords.io/api-reference/ads/show
get /projects/{project_id}/ads/{id}
Returns a single ad by ID.
# Update ad
Source: https://docs.beyondwords.io/api-reference/ads/update
put /projects/{project_id}/ads/{id}
Updates an ad in a project.
# Get ad analytics
Source: https://docs.beyondwords.io/api-reference/analytics/ad-analytics
get /projects/{project_id}/ads/{id}/analytics
Retrieve analytics for a single ad in a project.
# List ads analytics
Source: https://docs.beyondwords.io/api-reference/analytics/ads-analytics
get /projects/{project_id}/ads/analytics
Retrieve analytics overview for all ads in a project.
# Get content analytics
Source: https://docs.beyondwords.io/api-reference/analytics/content-analytics
get /projects/{project_id}/content/{id}/analytics
Retrieve analytics for some specific content.
# List content analytics
Source: https://docs.beyondwords.io/api-reference/analytics/contents-analytics
get /projects/{project_id}/content/analytics
Retrieves an analytics overview for all content items in a project
# Get organization analytics
Source: https://docs.beyondwords.io/api-reference/analytics/organization-analytics
get /organization/analytics
Retrieves analytics for all projects within an organization
# Get project analytics
Source: https://docs.beyondwords.io/api-reference/analytics/project-analytics
get /projects/{id}/analytics
Retrieves analytics for a project
# List projects analytics
Source: https://docs.beyondwords.io/api-reference/analytics/projects-analytics
get /projects/analytics
Retrieves an analytics overview for all projects within an organization
# Create background tracks
Source: https://docs.beyondwords.io/api-reference/background-tracks/create
post /background_tracks
Creates a background track in your organization.
# Delete background track
Source: https://docs.beyondwords.io/api-reference/background-tracks/delete
delete /background_tracks/{id}
Deletes a background track from your organization.
# List background tracks
Source: https://docs.beyondwords.io/api-reference/background-tracks/list
get /background_tracks
Lists background tracks available to your organization.
# Get background track
Source: https://docs.beyondwords.io/api-reference/background-tracks/show
get /background_tracks/{id}
Returns a background track from your organization.
# Update background track
Source: https://docs.beyondwords.io/api-reference/background-tracks/update
put /background_tracks/{id}
Updates a background track in your organization.
# Get content extraction settings
Source: https://docs.beyondwords.io/api-reference/content-extraction-settings/show
get /projects/{project_id}/content_extraction_settings
Returns extraction settings for a project, including JavaScript rendering and request header configuration.
# Update content extraction settings
Source: https://docs.beyondwords.io/api-reference/content-extraction-settings/update
put /projects/{project_id}/content_extraction_settings
Updates extraction settings for a project, including extraction mode and JavaScript rendering options.
# Create content filter
Source: https://docs.beyondwords.io/api-reference/content-filters/create
post /html_filters
Creates a content filter in your organization.
# Delete content filter
Source: https://docs.beyondwords.io/api-reference/content-filters/delete
delete /html_filters/{id}
Deletes a content filter from your organization.
# List content filters
Source: https://docs.beyondwords.io/api-reference/content-filters/list
get /html_filters
Lists content filters in your organization. Content filters control which HTML elements are included or excluded during extraction.
# Content filters overview
Source: https://docs.beyondwords.io/api-reference/content-filters/overview
Configure which HTML elements are included or excluded from audio generation
You can use the API to configure which HTML elements should be included or excluded from audio generation.
For example, to exclude `
` subheadings from audio generation, add a filter with `include: false`.
```json theme={null}
{
"element_type": "h2",
"include": false
}
```
Define filters by `element_type`, `element_class`, `element_id`, `element_data`, `element_xpath`, or `value`. Combine attributes for more targeted filters.
See [Content extraction](/docs-and-guides/integrations/content-extraction) for how content filters work with ingestion.
# Get content filter
Source: https://docs.beyondwords.io/api-reference/content-filters/show
get /html_filters/{id}
Returns a single content filter by ID.
# Update content filter
Source: https://docs.beyondwords.io/api-reference/content-filters/update
put /html_filters/{id}
Updates a content filter in your organization.
# Create and generate content
Source: https://docs.beyondwords.io/api-reference/content/create
post /projects/{project_id}/content
Submit a content item and queue audio or video generation
This is the primary endpoint for generating audio and video. Submit a new content item and BeyondWords queues processing based on the `type` you provide.
See [Processing types](/api-reference/content/processing-types) for request body examples. After creating, set up [webhooks](/api-reference/webhooks/list) to be notified when processing completes via [`audio.updated`](/docs-and-guides/integrations/webhooks#audio-updated-payload).
To update an existing item and re-run generation, use [Update content](/api-reference/content/update) or [Regenerate content](/api-reference/content/regenerate).
# Delete content
Source: https://docs.beyondwords.io/api-reference/content/delete
delete /projects/{project_id}/content/{id}
Deletes a content item from your project.
# Generating content
Source: https://docs.beyondwords.io/api-reference/content/introduction
How to generate audio and video through the content API
To generate audio or video, submit a content item via [Create and generate content](/api-reference/content/create). That endpoint queues TTS and media processing — it is the main entry point for integrations.
Most workflows follow three steps:
1. **Create** — POST article HTML, manual segments, or an audio upload. See [Processing types](/api-reference/content/processing-types).
2. **Wait for completion** — Use [webhooks](/api-reference/webhooks/list) to be notified when `status` is `processed` (or handle `error` / `skipped`). See the [Webhooks guide](/docs-and-guides/integrations/webhooks).
3. **Retrieve** — Call [Get content](/api-reference/content/show) with `?segments=full` when you need segment data for the player.
Submit content and start audio or video generation.
Choose `auto_segment`, `manual_segment`, or `audio_upload`.
Properties returned on every content item.
Re-run generation after updating an existing item.
## Endpoints
| Goal | Endpoint |
| ----------------------------------------- | ------------------------------------------------------------ |
| Generate audio or video for new content | [Create and generate content](/api-reference/content/create) |
| Fetch a content item | [Get content](/api-reference/content/show) |
| Update a content item | [Update content](/api-reference/content/update) |
| Replace AI-generated audio with an upload | [Replace audio](/api-reference/content/replace-audio) |
| Delete a content item | [Delete content](/api-reference/content/delete) |
| Re-generate after edits | [Regenerate content](/api-reference/content/regenerate) |
| Generate a summary | [Summarize content](/api-reference/content/summarize) |
| Upload audio for `audio_upload` | [Upload file](/api-reference/content/upload) |
| List content items | [List content](/api-reference/content/list) |
The other API reference sections — **Extraction**, **Pronunciation settings**, **Script settings**, **Video settings**, and **Background music settings** — configure project defaults. They do not generate content on their own. Use the **Items** endpoints below the developer guide for that.
## Identifiers
| Field | Use |
| ------------ | -------------------------------------------------------------------------------------------------- |
| `id` | BeyondWords UUID. Use with player and analytics endpoints. |
| `source_id` | Your CMS or feed identifier. Use for upserts and [syndication](/api-reference/content/syndicated). |
| `source_url` | Public article URL. Use with [player by source URL](/api-reference/player/by-source-url). |
## Processing status
| Status | Meaning |
| ------------ | --------------------------------------------------------------------------------------------------- |
| `draft` | Created but not queued for generation. |
| `queued` | Waiting to be processed. |
| `processing` | Audio or video is being generated. |
| `processed` | Generation complete. Audio and segment data are available. |
| `skipped` | Generation was skipped (for example, insufficient credits). |
| `error` | Generation failed. Update the item or call [Regenerate content](/api-reference/content/regenerate). |
## Related guides
* [Content items](/docs-and-guides/content/content-items) — dashboard concepts and metadata
* [Webhooks](/docs-and-guides/integrations/webhooks) — get notified when processing completes
* [Replace audio](/api-reference/content/replace-audio) — swap AI-generated audio for an uploaded file via the API
* [Upload audio](/docs-and-guides/tools/upload-audio) — dashboard workflow for `audio_upload`
# List content
Source: https://docs.beyondwords.io/api-reference/content/list
get /projects/{project_id}/content
Lists content items within a project.
# Content object
Source: https://docs.beyondwords.io/api-reference/content/overview
Properties on the content resource
A content item belongs to a project and is returned by [List](/api-reference/content/list), [Create](/api-reference/content/create), [Get](/api-reference/content/show), and [Update](/api-reference/content/update) content endpoints.
See [Processing types](/api-reference/content/processing-types) for how `type` affects what you send in requests. For [syndication](/api-reference/content/syndicated), check `is_copy` to distinguish originals from reused audio.
## Properties
| Property | Type | Description |
| --------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Unique UUID for the object. |
| `status` | string | Processing state: `draft`, `queued`, `processing`, `processed`, `skipped`, or `error`. |
| `type` | string | `auto_segment`, `manual_segment`, or `audio_upload`. |
| `title` | string | Content title. |
| `summary` | string | Content summary. |
| `body` | string | Content body. |
| `source_id` | string | External identifier from your CMS or feed. |
| `source_url` | string | Public URL of the source article. |
| `author` | string | Content author. |
| `image_url` | string | Feature image URL. |
| `metadata` | object | Arbitrary key-value metadata (for example, `{"category": "sport"}`). |
| `is_copy` | boolean | `true` when audio is reused from another project via syndication. |
| `audio` | array | Generated audio files for the full content item. |
| `audio.id` | integer | Audio object identifier. |
| `audio.content_type` | string | MIME type of the audio file. |
| `audio.url` | string | URL of the generated audio file. |
| `audio.duration` | integer | Duration in milliseconds. |
| `video` | array | Generated video files for the full content item. |
| `segments` | array | Segments in the content item. Use `?segments=full` on GET to populate. |
| `segments.id` | integer | Segment identifier. |
| `segments.marker` | string | Marker for [segment-level playback](/docs-and-guides/distribution/player/developer-guides/segment-detection). |
| `segments.section` | string | `title`, `summary`, or `body`. |
| `segments.content_type` | string | `text`, `audio`, `image`, or `video`. |
| `segments.text` | string | Plain text when `content_type` is `text`. |
| `segments.audio_url` | string | Audio URL when `content_type` is `audio`. |
| `segments.image_url` | string | Image URL when `content_type` is `image`. |
| `segments.video_url` | string | Video URL when `content_type` is `video`. |
| `segments.video_settings` | object | Settings for video segments. |
| `segments.video_settings.plays_in_background` | boolean | When `true`, the clip plays under narration (timed like an image and looping as needed) instead of as a standalone scene. Defaults to `false`. |
| `segments.start_time` | integer | Start time in milliseconds. |
| `segments.duration` | integer | Duration in milliseconds. |
| `published` | boolean | Whether the item appears in players and feeds. |
| `publish_date` | string | Publish or scheduled publish time (ISO 8601). |
| `ads_enabled` | boolean | Whether ads play for this item in players. |
| `auto_segment_updates_enabled` | boolean | Regenerate audio when `title`, `summary`, or `body` changes (`auto_segment` only). |
| `created` | string | Creation time (ISO 8601). |
| `updated` | string | Last update time (ISO 8601). |
# Processing types
Source: https://docs.beyondwords.io/api-reference/content/processing-types
How auto_segment, manual_segment, and audio_upload generate audio
The `type` property on a content item determines how BeyondWords processes it. Set `type` when you [create](/api-reference/content/create) or [update](/api-reference/content/update) an item.
## auto\_segment
Recommended for most publishers. Submit HTML for `body` (and optionally `title` and `summary`). BeyondWords segments the body automatically and generates audio asynchronously.
```json theme={null}
{
"type": "auto_segment",
"title": "
Article title
",
"summary": "
Summary
",
"body": "
First paragraph.
Second paragraph.
",
"source_id": "cms-article-123",
"source_url": "https://example.com/article",
"published": true
}
```
The initial response returns `status: "queued"` and an empty `segments` array. After processing, call [Get content](/api-reference/content/show) with `?segments=full` to retrieve segment markers and timings.
### Pause markers
Add verbal pauses in HTML:
```html theme={null}
The policy reduces emissions. In practice, it may do the opposite.
```
Pause values are in seconds (max 3), with up to one decimal place.
## manual\_segment
For editorial tools or fine-grained control. Do **not** send `title`, `summary`, or `body`. Instead, send a `segments` array.
| Property | Options |
| -------------- | ------------------------------------------------------------------- |
| `section` | `title`, `summary`, or `body` |
| `content_type` | `text` (TTS), `audio` (upload URL), `image` (for video), or `video` |
```json theme={null}
{
"type": "manual_segment",
"source_id": "cms-article-123",
"segments": [
{ "section": "title", "content_type": "text", "text": "Article title" },
{ "section": "body", "content_type": "text", "text": "First paragraph.", "marker": "para-1" },
{ "section": "body", "content_type": "text", "text": "Second paragraph.", "marker": "para-2" },
{
"section": "body",
"content_type": "video",
"video_url": "https://example.com/clip.mp4",
"video_settings": {
"plays_in_background": true
}
}
]
}
```
Use `marker` on text segments when you need [segment-level playback](/docs-and-guides/distribution/player/developer-guides/segment-detection) in the player.
Set `video_settings.plays_in_background` to `true` on video segments so the clip plays under narration instead of as a standalone scene. Background clips follow visual timing, loop as needed, and do not extend the video length.
For pauses in plain text, use ``.
## audio\_upload
For podcasts or pre-recorded audio. Submit a `title`, `body` (shown in the player or feeds as metadata), and one audio segment:
```json theme={null}
{
"type": "audio_upload",
"title": "Episode title",
"body": "Episode description",
"source_id": "episode-42",
"segments": [
{ "section": "title", "content_type": "audio", "audio_url": "https://example.com/episode.mp3" }
],
"published": true
}
```
The title and body are not converted to speech. Use [Upload file](/api-reference/content/upload) if you need to upload the audio file to BeyondWords first. To replace AI-generated audio on an existing item, see [Replace audio](/api-reference/content/replace-audio).
## Choosing a type
| Type | Best for |
| ---------------- | ---------------------------------------------------------- |
| `auto_segment` | CMS integrations, article-to-audio pipelines |
| `manual_segment` | Custom editorial UIs, mixed media, precise segment control |
| `audio_upload` | Podcasts, human-recorded narration |
# Regenerate content
Source: https://docs.beyondwords.io/api-reference/content/regenerate
post /projects/{project_id}/content/{id}/regenerate
Re-run audio or video generation for an existing content item
Use this endpoint when content has already been created and you need to regenerate its audio or video — for example, after updating the `title`, `body`, or `summary` on an `auto_segment` item.
# Replace audio
Source: https://docs.beyondwords.io/api-reference/content/replace-audio
Replace AI-generated audio with an uploaded audio file
You can use the API to replace the AI-generated audio on an existing content item with your own audio file. This is useful if you want to switch to a human-recorded version while keeping the same content item, player embeds, distributions, and analytics intact.
The same workflow applies whether you're replacing a single item or doing it in bulk.
To replace audio through the dashboard instead, go to **Content → Items**, click **••• → Replace** alongside the item, and follow the steps in [Upload audio](/docs-and-guides/tools/upload-audio#replace-an-existing-content-item).
## Replace audio on an existing content item
To replace audio on an existing content item, send a `PUT` request to [Update content](/api-reference/content/update) with `"type": "audio_upload"`. This switches the content item's processing type and replaces its audio with the file you provide.
The `audio_url` must be a publicly accessible URL. BeyondWords fetches the file from that URL when it processes the request — the file is not uploaded directly in the request body.
```json theme={null}
{
"type": "audio_upload",
"auto_segment_updates_enabled": false,
"segments": [
{
"section": "title",
"content_type": "audio",
"audio_url": "https://example.com/your-replacement-audio.mp3"
}
]
}
```
The `{id}` path parameter accepts either the content UUID or the original `source_id` you set when creating the item.
Set `"auto_segment_updates_enabled": false` to prevent future text edits from triggering AI regeneration and overwriting your uploaded audio. Without this, editing the `title`, `summary`, or `body` of the item will queue a new AI generation run.
## Create a new content item with uploaded audio
If you're creating a new content item from scratch with an audio file rather than replacing an existing one, use `POST /projects/{project_id}/content` with `"type": "audio_upload"`:
```json theme={null}
{
"type": "audio_upload",
"title": "My article title",
"body": "Article body text",
"source_id": "your-cms-article-id",
"published": true,
"segments": [
{
"section": "title",
"content_type": "audio",
"audio_url": "https://example.com/your-audio-file.mp3"
}
]
}
```
See [Processing types](/api-reference/content/processing-types#audio_upload) for more on the `audio_upload` type.
## Upload a file to BeyondWords first
If your audio file isn't already hosted at a public URL, use [Upload file](/api-reference/content/upload) to upload it to BeyondWords storage first. That endpoint returns a URL you can then pass as `audio_url`.
# Get content
Source: https://docs.beyondwords.io/api-reference/content/show
get /projects/{project_id}/content/{id}
Returns a single content item from your project. Use the `segments` query parameter to include segment-level data.
# Summarize content
Source: https://docs.beyondwords.io/api-reference/content/summarize
post /projects/{project_id}/content/{id}/summarize
Generates a script summary for a content item using the project script settings.
# Syndication
Source: https://docs.beyondwords.io/api-reference/content/syndicated
Reuse audio across content items and projects to save credits
Syndication lets publishers reuse audio across different projects — such as multiple websites — without generating new audio for each content item. Credits are only consumed when audio is **generated**. A syndicated copy reuses an existing audio file, so you are not charged again for TTS on that item.
This feature must be enabled by the BeyondWords team. [Contact us](/docs-and-guides/support/get-support) to discuss enabling it for your account.
## Why use syndication
| Benefit | Detail |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Save credits** | The first content item with a given `source_id` generates audio as usual. Subsequent items in other projects reuse that audio and skip generation. |
| **Consistency** | The same spoken audio plays across every site or project that syndicates the item. |
| **Per-project distribution** | Each project still gets its own content item, so you can use different `source_url`, `metadata`, playlists, and analytics per site. |
## Example
A publisher runs two websites (separate projects) and publishes the same article on both:
1. **Project A** — [Create content](/api-reference/content/create) with `source_id: "shared-article-001"`. Audio is generated and credits are consumed.
2. **Project B** — Create content with the same `source_id`. A new content item is created, but it references Project A's audio file. No new audio is generated and no additional TTS credits are used.
## How it works
When you POST to [Create content](/api-reference/content/create) with a `source_id`:
1. If no item with that `source_id` exists in your organization, audio is generated as usual.
2. If an item with that `source_id` exists in another project, the existing audio file is reused. A new content item is still created in the target project.
Matching is by `source_id` across projects within your organization.
## Constraints
Syndication trades flexibility for credit savings. Keep these limitations in mind:
* **Feature gate** — Syndication must be enabled on your account before it takes effect.
* **Matching key** — Reuse only happens when the `source_id` matches an existing item in another project. Different IDs always trigger new generation.
* **Locked audio content** — Syndicated copies cannot have different spoken content. Inherited fields cannot be overwritten on the child item (see below).
* **Parent updates** — Changes to the parent item's audio or text flow to syndicated copies. You cannot independently edit title, body, summary, or segments on a child.
* **Project-scoped metadata only** — You can set project-specific values such as `source_url` and `metadata`, but not the core audio-bearing fields.
## Identifying copies
Use `is_copy` in the API response or the [`audio.updated`](/docs-and-guides/integrations/webhooks#audio-updated-payload) webhook payload:
| Value | Meaning |
| ---------------- | ------------------------------------------------------------------- |
| `is_copy: false` | Original item that generated the audio. Credits were consumed here. |
| `is_copy: true` | Child item that reuses existing audio. No TTS credits consumed. |
## Inherited fields
When audio is reused, these fields are inherited from the parent and **cannot be overwritten** on the child:
* `status`, `title`, `type`, `summary`, `body`, `audio`, `video`, `summarization`, `segments`
You can still set project-specific fields such as `source_url` and `metadata` in the create request.
# Update content
Source: https://docs.beyondwords.io/api-reference/content/update
put /projects/{project_id}/content/{id}
Updates a content item in your project.
**Segment replace semantics:** When you include a `segments` array, it becomes the new complete set of segments.
Segments with an `id` are kept or updated; segments without an `id` are added; segments omitted from the array are deleted.
**Regeneration:** Only changed segments are re-synthesized — unchanged segment audio is reused.
A PUT that changes segments triggers regeneration automatically. Use POST `/content/{id}/regenerate` only when
you need to regenerate without segment changes (for example, after updating voice or preprocessing settings).
# Upload file
Source: https://docs.beyondwords.io/api-reference/content/upload
post /upload
Uploads a file to BeyondWords storage. Use the returned URL when creating or updating content items that reference uploaded audio or other assets.
# Ghost posts webhook
Source: https://docs.beyondwords.io/api-reference/ghost/posts
post /projects/{project_id}/integrations/ghost/posts
Webhook endpoint that Ghost calls when posts are created or updated. Creates or updates content in BeyondWords when a post is published or scheduled.
Webhook endpoint that Ghost calls when posts are created or updated. Creates or updates content in BeyondWords when a post is published or scheduled.
Configure this URL in your Ghost custom integration. Authenticate requests with the `api_key` query parameter using your project API key.
See the [Ghost integration guide](/docs-and-guides/integrations/publishing-platforms/ghost).
# Get Ghost integration
Source: https://docs.beyondwords.io/api-reference/ghost/show
get /projects/{project_id}/integrations/ghost
Returns Ghost integration settings for a project, including whether the integration is enabled and the configured API URL.
Returns the Ghost integration settings for a project, including whether the integration is enabled and the configured API URL.
See the [Ghost integration guide](/docs-and-guides/integrations/publishing-platforms/ghost) for dashboard setup.
# Test Ghost integration
Source: https://docs.beyondwords.io/api-reference/ghost/test
post /projects/{project_id}/integrations/ghost/test
Test the Ghost API URL and Admin API key before saving integration settings.
Test the Ghost API URL and Admin API key before saving integration settings.
# Update Ghost integration
Source: https://docs.beyondwords.io/api-reference/ghost/update
put /projects/{project_id}/integrations/ghost
Enable or update the Ghost integration for a project. Set `enabled`, `api_url`, and `admin_api_key` to connect BeyondWords to your Ghost site.
Enable or update the Ghost integration for a project. Set `enabled`, `api_url`, and `admin_api_key` to connect BeyondWords to your Ghost site.
See the [Ghost integration guide](/docs-and-guides/integrations/publishing-platforms/ghost) for setup instructions.
# List languages
Source: https://docs.beyondwords.io/api-reference/languages/list
get /organization/languages
Lists languages available to your organization.
# Get Magic Embed settings
Source: https://docs.beyondwords.io/api-reference/magic-embed/show
get /projects/{project_id}/integrations/client_side
Returns Magic Embed settings for a project, including whether the integration is enabled and the list of allowed domains.
Returns Magic Embed settings for a project, including whether the integration is enabled and the list of allowed domains.
See the [Magic Embed guide](/docs-and-guides/integrations/magic-embed) for setup instructions.
# Update Magic Embed settings
Source: https://docs.beyondwords.io/api-reference/magic-embed/update
put /projects/{project_id}/integrations/client_side
Enable or update Magic Embed for a project. Set `enabled` and `allowed_domains` to control where the Magic Embed script can ingest content.
Enable or update Magic Embed for a project. Set `enabled` and `allowed_domains` to control where the Magic Embed script can ingest content.
See the [Magic Embed guide](/docs-and-guides/integrations/magic-embed) for setup instructions.
# Overview
Source: https://docs.beyondwords.io/api-reference/overview
Learn about the BeyondWords API
The BeyondWords API is a RESTful API that provides headless access to the entire platform. It has predictable resource-oriented URLs, accepts form-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.
This reference covers the endpoints most integrators need.
## Base URL
All requests use the `v1` API. Prepend this base URL to every endpoint path:
```text theme={null}
https://api.beyondwords.io/v1/
```
For example, listing content items for a project:
```text theme={null}
https://api.beyondwords.io/v1/projects/{project_id}/content
```
Replace `{project_id}` with your Project ID from **Settings → Integrations → API** in the dashboard.
## Authentication
Every request requires your API key in the `X-Api-Key` header. Find your Project ID and API Key under **Settings → Integrations → API**.
```bash theme={null}
-H "X-Api-Key: YOUR_API_KEY"
```
Keep your API key secret. Do not expose it in client-side code or public repositories.
## Rate limits
To help keep our service stable and reliable, API requests are subject to the following rate limits:
| Request type | Limit |
| ------------------ | --------------------------- |
| `GET` requests | 100 requests per 60 seconds |
| All other requests | 25 requests per 60 seconds |
### Handling rate limits
Design your integration to avoid exceeding these limits. We recommend implementing client-side rate limiting so requests are spread evenly over time rather than sent in large bursts.
If your application receives a `429 Too Many Requests` response, it should wait before retrying the request. We recommend retrying after approximately 30 seconds.
### Recommended request patterns
To avoid unnecessary API usage and improve reliability:
* Do not continuously poll API endpoints for status updates.
* Use [webhooks](/api-reference/webhooks/list) where possible, especially for status or state-change events.
* Add a global concurrency limit across your application, not just per worker or per process.
* Cache `GET` responses where appropriate.
* Avoid running multiple parallel jobs that may unintentionally create request spikes.
Following these practices will help your integration remain reliable and avoid temporary throttling.
## Resources
### Content
Content items are the core resource. See the [developer guide](/api-reference/content/introduction) for processing types, the [content object](/api-reference/content/overview), and [syndication](/api-reference/content/syndicated).
**Example: create content with `auto_segment`**
Send `title` as plain text. `body` should be HTML.
```bash Request theme={null}
curl -X POST "https://api.beyondwords.io/v1/projects/{project_id}/content" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "auto_segment",
"title": "Article title",
"body": "
",
"source_id": "cms-article-123",
"source_url": "https://example.com/article",
"published": true,
"segments": [],
"audio": [],
"video": [],
"created": "2025-06-08T10:00:00.000Z",
"updated": "2025-06-08T10:00:00.000Z"
}
```
Set up [webhooks](/api-reference/webhooks/list) to be notified when processing completes. See the [Webhooks guide](/docs-and-guides/integrations/webhooks) for setup.
**Endpoints**
* [Create and generate content](/api-reference/content/create)
* [Get content](/api-reference/content/show)
* [Update content](/api-reference/content/update)
* [Delete content](/api-reference/content/delete)
* [Regenerate content](/api-reference/content/regenerate)
* [Summarize content](/api-reference/content/summarize)
* [Upload file](/api-reference/content/upload)
* [List content](/api-reference/content/list)
### Extraction
Control how text is extracted from webpages when ingesting content.
**Filters**
* [Content filters overview](/api-reference/content-filters/overview)
* [List content filters](/api-reference/content-filters/list)
* [Create content filter](/api-reference/content-filters/create)
* [Get content filter](/api-reference/content-filters/show)
* [Update content filter](/api-reference/content-filters/update)
* [Delete content filter](/api-reference/content-filters/delete)
**Extraction settings**
* [Get content extraction settings](/api-reference/content-extraction-settings/show)
* [Update content extraction settings](/api-reference/content-extraction-settings/update)
### Pronunciation settings
* [List pronunciation rules](/api-reference/rules/list)
* [Create pronunciation rule](/api-reference/rules/create)
* [Get pronunciation rule](/api-reference/rules/show)
* [Update pronunciation rule](/api-reference/rules/update)
* [Delete pronunciation rule](/api-reference/rules/delete)
### Script settings
* [Get project script settings](/api-reference/summarization-settings/show)
* [Update project script settings](/api-reference/summarization-settings/update)
**Script templates**
* [List script templates](/api-reference/summarization-settings-templates/list)
* [Create script template](/api-reference/summarization-settings-templates/create)
* [Get script template](/api-reference/summarization-settings-templates/show)
* [Update script template](/api-reference/summarization-settings-templates/update)
* [Delete script template](/api-reference/summarization-settings-templates/delete)
### Video settings
* [Get video settings](/api-reference/video-settings/show)
* [Update video settings](/api-reference/video-settings/update)
**Video templates**
* [List video settings templates](/api-reference/video-settings-templates/list)
* [Create video settings template](/api-reference/video-settings-templates/create)
* [Get video settings template](/api-reference/video-settings-templates/show)
* [Update video settings template](/api-reference/video-settings-templates/update)
* [Delete video settings template](/api-reference/video-settings-templates/delete)
* [Preview video settings template](/api-reference/video-settings-templates/preview)
### Background music settings
* [List background tracks](/api-reference/background-tracks/list)
* [Create background tracks](/api-reference/background-tracks/create)
* [Get background track](/api-reference/background-tracks/show)
* [Update background track](/api-reference/background-tracks/update)
* [Delete background track](/api-reference/background-tracks/delete)
### Voices
**Voices**
* [List voices](/api-reference/voices/list)
* [Get voice](/api-reference/voices/show)
* [Update voice](/api-reference/voices/update)
* [Delete voice](/api-reference/voices/delete)
**Voice generation**
* [Design voice previews](/api-reference/voices/design-previews)
* [Finalize voice design](/api-reference/voices/finalize-design)
**Speakers**
* [List speakers](/api-reference/speakers/list)
* [Create speaker](/api-reference/speakers/create)
* [Get speaker](/api-reference/speakers/show)
* [Update speaker](/api-reference/speakers/update)
* [Delete speaker](/api-reference/speakers/delete)
**Voice cloning**
* [Create voice](/api-reference/voices/create)
* [Clone instant voice](/api-reference/voices/clone)
**Languages**
* [List languages](/api-reference/languages/list)
### Distribution
**Player**
* [Get player by content ID](/api-reference/player/by-content-id)
* [Get player by source ID](/api-reference/player/by-source-id)
* [Get player by source URL](/api-reference/player/by-source-url)
* [Get player by playlist ID](/api-reference/player/by-playlist-id)
* [Get player by multiple identifiers](/api-reference/player/by-identifiers)
**Player settings**
* [Get player settings](/api-reference/player-settings/show)
* [Update player settings](/api-reference/player-settings/update)
**Access tiers**
* [List access tiers](/api-reference/access-tiers/list)
* [Create access tier](/api-reference/access-tiers/create)
* [Get access tier](/api-reference/access-tiers/show)
* [Update access tier](/api-reference/access-tiers/update)
**Playlists**
* [List playlists](/api-reference/playlists/list)
* [Create playlist](/api-reference/playlists/create)
* [Get playlist](/api-reference/playlists/show)
* [Update playlist](/api-reference/playlists/update)
* [Delete playlist](/api-reference/playlists/delete)
**Podcast feeds**
* [Get podcast feed settings](/api-reference/podcast-feeds/get-settings)
* [Update podcast feed settings](/api-reference/podcast-feeds/update-settings)
### Integrations
**Webhooks**
* [List webhooks](/api-reference/webhooks/list)
* [Create webhook](/api-reference/webhooks/create)
* [Get webhook](/api-reference/webhooks/show)
* [Update webhook](/api-reference/webhooks/update)
* [Delete webhook](/api-reference/webhooks/delete)
* [Test webhook](/api-reference/webhooks/test)
**RSS feed importer**
* [List RSS feed imports](/api-reference/rss-feed-importer/list)
* [Create RSS feed import](/api-reference/rss-feed-importer/create)
* [Get RSS feed import](/api-reference/rss-feed-importer/show)
* [Update RSS feed import](/api-reference/rss-feed-importer/update)
* [Delete RSS feed import](/api-reference/rss-feed-importer/delete)
* [Run RSS feed import](/api-reference/rss-feed-importer/run)
* [Test RSS feed import](/api-reference/rss-feed-importer/test)
**Magic Embed**
* [Get Magic Embed settings](/api-reference/magic-embed/show)
* [Update Magic Embed settings](/api-reference/magic-embed/update)
**Ghost**
* [Get Ghost integration](/api-reference/ghost/show)
* [Update Ghost integration](/api-reference/ghost/update)
* [Test Ghost integration](/api-reference/ghost/test)
* [Ghost posts webhook](/api-reference/ghost/posts)
### Analytics
**Content**
* [Get organization analytics](/api-reference/analytics/organization-analytics)
* [List projects analytics](/api-reference/analytics/projects-analytics)
* [Get project analytics](/api-reference/analytics/project-analytics)
* [List content analytics](/api-reference/analytics/contents-analytics)
* [Get content analytics](/api-reference/analytics/content-analytics)
**Ads**
* [List ads analytics](/api-reference/analytics/ads-analytics)
* [Get ad analytics](/api-reference/analytics/ad-analytics)
### Monetization
**Ads**
* [List ads](/api-reference/ads/list)
* [Create ad](/api-reference/ads/create)
* [Get ad](/api-reference/ads/show)
* [Update ad](/api-reference/ads/update)
* [Delete ad](/api-reference/ads/delete)
### Organization
**Projects**
* [List projects](/api-reference/projects/list)
* [Create project](/api-reference/projects/create)
* [Get project](/api-reference/projects/show)
* [Update project](/api-reference/projects/update)
* [Delete project](/api-reference/projects/delete)
* [Get project voices](/api-reference/projects/voices)
# Get player settings
Source: https://docs.beyondwords.io/api-reference/player-settings/show
get /projects/{project_id}/player_settings
Get the player settings for your project
# Update player settings
Source: https://docs.beyondwords.io/api-reference/player-settings/update
put /projects/{project_id}/player_settings
Updates the player settings for a project
# Get player by content ID
Source: https://docs.beyondwords.io/api-reference/player/by-content-id
get /projects/{project_id}/player/by_content_id/{id}
Returns player configuration and media for a content item, looked up by its BeyondWords content ID or legacy ID.
# Get player by multiple identifiers
Source: https://docs.beyondwords.io/api-reference/player/by-identifiers
get /projects/{project_id}/player/by_identifiers/{array}
Returns player configuration and media for a content item, looked up by multiple identifiers passed as a URL-encoded JSON array.
# Get player by playlist ID
Source: https://docs.beyondwords.io/api-reference/player/by-playlist-id
get /projects/{project_id}/player/by_playlist_id/{id}
Returns player configuration and media for a playlist, including continuous playback across its items.
# Get player by source ID
Source: https://docs.beyondwords.io/api-reference/player/by-source-id
get /projects/{project_id}/player/by_source_id/{id}
Returns player configuration and media for a content item, looked up by the `source_id` you assigned when creating it.
# Get player by source URL
Source: https://docs.beyondwords.io/api-reference/player/by-source-url
get /projects/{project_id}/player/by_source_url/{url}
Returns player configuration and media for a content item, looked up by its canonical `source_url`.
# Create playlist
Source: https://docs.beyondwords.io/api-reference/playlists/create
post /projects/{project_id}/playlists
Creates a playlist in a project.
# Delete playlist
Source: https://docs.beyondwords.io/api-reference/playlists/delete
delete /projects/{project_id}/playlists/{id}
Deletes a custom playlist from a project.
# List playlists
Source: https://docs.beyondwords.io/api-reference/playlists/list
get /projects/{project_id}/playlists
Lists playlists configured for a project.
# Get playlist
Source: https://docs.beyondwords.io/api-reference/playlists/show
get /projects/{project_id}/playlists/{id}
Returns a single playlist by ID.
# Update playlist
Source: https://docs.beyondwords.io/api-reference/playlists/update
put /projects/{project_id}/playlists/{id}
Updates a playlist in a project.
# Get podcast feed settings
Source: https://docs.beyondwords.io/api-reference/podcast-feeds/get-settings
get /projects/{project_id}/playlists/{playlist_id}/settings
Returns RSS feed settings for a playlist.
Gets the podcast feed settings for a playlist. Each podcast feed is linked to a playlist — use the playlist ID when calling this endpoint.
# Update podcast feed settings
Source: https://docs.beyondwords.io/api-reference/podcast-feeds/update-settings
put /projects/{project_id}/playlists/{playlist_id}/settings
Updates RSS feed settings for a playlist.
Updates the podcast feed settings for a playlist. Each podcast feed is linked to a playlist — use the playlist ID when calling this endpoint.
# Create project
Source: https://docs.beyondwords.io/api-reference/projects/create
post /projects
Creates a project in your organization.
# Delete project
Source: https://docs.beyondwords.io/api-reference/projects/delete
delete /projects/{id}
Deletes a project from your organization.
# List projects
Source: https://docs.beyondwords.io/api-reference/projects/list
get /projects
Lists all projects in your organization.
# Get project
Source: https://docs.beyondwords.io/api-reference/projects/show
get /projects/{id}
Returns a single project by ID.
# Update project
Source: https://docs.beyondwords.io/api-reference/projects/update
put /projects/{id}
Updates a project in your organization.
# Get project voices
Source: https://docs.beyondwords.io/api-reference/projects/voices
get /projects/{id}/voices
Lists the voices assigned to a project.
# Create RSS feed import
Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/create
post /projects/{project_id}/integrations/rss_importer/feeds
Creates an RSS feed import for a project. BeyondWords fetches the feed on a schedule and creates content items from matching entries.
# Delete RSS feed import
Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/delete
delete /projects/{project_id}/integrations/rss_importer/feeds/{id}
Deletes an RSS feed import from a project.
# List RSS feed imports
Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/list
get /projects/{project_id}/integrations/rss_importer/feeds
Lists RSS feed imports configured for a project.
# Run RSS feed import
Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/run
post /projects/{project_id}/integrations/rss_importer/feeds/{id}/run
Runs an RSS feed import immediately, bypassing the configured import interval.
# Get RSS feed import
Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/show
get /projects/{project_id}/integrations/rss_importer/feeds/{id}
Returns a single RSS feed import by ID.
# Test RSS feed import
Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/test
post /projects/{project_id}/integrations/rss_importer/feeds/test
Fetches and parses an RSS feed without saving an import. Use this to preview which items would be ingested and verify field mapping.
# Update RSS feed import
Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/update
put /projects/{project_id}/integrations/rss_importer/feeds/{id}
Updates an RSS feed import. You can change the feed URL, field mapping, import interval, and whether the import is enabled.
# Create pronunciation rule
Source: https://docs.beyondwords.io/api-reference/rules/create
post /rules
Creates a pronunciation rule in your organization.
# Delete pronunciation rule
Source: https://docs.beyondwords.io/api-reference/rules/delete
delete /rules/{id}
Deletes a pronunciation rule from your organization.
# List pronunciation rules
Source: https://docs.beyondwords.io/api-reference/rules/list
get /rules
Lists pronunciation rules in your organization.
# Get pronunciation rule
Source: https://docs.beyondwords.io/api-reference/rules/show
get /rules/{id}
Returns a pronunciation rule from your organization.
# Update pronunciation rule
Source: https://docs.beyondwords.io/api-reference/rules/update
put /rules/{id}
Updates a pronunciation rule in your organization.
# Create speaker
Source: https://docs.beyondwords.io/api-reference/speakers/create
post /speakers
Creates a speaker in your organization
Creates a speaker in your organization. Required before creating a [Clone instant voice](/api-reference/voices/clone) or [Create voice](/api-reference/voices/create) clone.
See [Voice cloning](/docs-and-guides/voices/voice-cloning) for the full workflow.
# Delete speaker
Source: https://docs.beyondwords.io/api-reference/speakers/delete
delete /speakers/{id}
Deletes a speaker
Deletes a speaker from your organization.
# List speakers
Source: https://docs.beyondwords.io/api-reference/speakers/list
get /speakers
Lists all speakers in your organization
Lists speakers in your organization. Speakers represent the people behind custom and cloned voices.
Use the returned `id` as `speaker_id` when you [create a voice](/api-reference/voices/create).
# Get speaker
Source: https://docs.beyondwords.io/api-reference/speakers/show
get /speakers/{id}
Gets a speaker from your organization
Gets a speaker from your organization, including associated voices.
# Update speaker
Source: https://docs.beyondwords.io/api-reference/speakers/update
put /speakers/{id}
Updates a speaker in your organization
Updates a speaker in your organization.
# Create script template
Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/create
post /summarization_settings_templates
Creates a script template in your organization.
# Delete script template
Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/delete
delete /summarization_settings_templates/{slug}
Deletes a script template from your organization.
# List script templates
Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/list
get /summarization_settings_templates
Lists script templates available in your organization.
# Get script template
Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/show
get /summarization_settings_templates/{slug}
Returns a single script template by slug.
# Update script template
Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/update
put /summarization_settings_templates/{slug}
Updates a script template in your organization.
# Get project script settings
Source: https://docs.beyondwords.io/api-reference/summarization-settings/show
get /projects/{project_id}/summarization_settings
Returns script settings for a project, including AI model configuration used when generating scripts and summaries.
# Update project script settings
Source: https://docs.beyondwords.io/api-reference/summarization-settings/update
put /projects/{project_id}/summarization_settings
Updates script settings for a project, including the default AI model, prompt, and template used for script generation.
# Create video settings template
Source: https://docs.beyondwords.io/api-reference/video-settings-templates/create
post /video_settings_templates
Creates a video settings template in your organization.
# Delete video settings template
Source: https://docs.beyondwords.io/api-reference/video-settings-templates/delete
delete /video_settings_templates/{slug}
Deletes a video settings template from your organization.
# List video settings templates
Source: https://docs.beyondwords.io/api-reference/video-settings-templates/list
get /video_settings_templates
Lists video settings templates available to your organization.
# Preview video settings template
Source: https://docs.beyondwords.io/api-reference/video-settings-templates/preview
post /video_settings_templates/preview
Generates a preview video from a video settings template.
# Get video settings template
Source: https://docs.beyondwords.io/api-reference/video-settings-templates/show
get /video_settings_templates/{slug}
Returns a video settings template from your organization.
# Update video settings template
Source: https://docs.beyondwords.io/api-reference/video-settings-templates/update
put /video_settings_templates/{slug}
Updates a video settings template in your organization.
# Get video settings
Source: https://docs.beyondwords.io/api-reference/video-settings/show
get /projects/{project_id}/video_settings
Returns video generation settings for a project.
# Update video settings
Source: https://docs.beyondwords.io/api-reference/video-settings/update
put /projects/{project_id}/video_settings
Updates video generation settings for a project.
# Clone instant voice
Source: https://docs.beyondwords.io/api-reference/voices/clone
post /organization/voices/{id}/clone
Clones a voice from cloning_consent and an uploaded utterance
# Create voice
Source: https://docs.beyondwords.io/api-reference/voices/create
post /organization/voices
Creates a voice in your organization.
Creates a custom voice in your organization. For instant and professional clones, create a [speaker](/api-reference/speakers/create) first and pass its `id` as `speaker_id`.
For generated voices, use [Design voice previews](/api-reference/voices/design-previews) and [Finalize voice design](/api-reference/voices/finalize-design) instead.
# Delete voice
Source: https://docs.beyondwords.io/api-reference/voices/delete
delete /organization/voices/{id}
Deletes a voice from your organization.
# Design voice previews
Source: https://docs.beyondwords.io/api-reference/voices/design-previews
post /organization/voices/design_previews
Generate voice preview audio from a text description
Generate voice preview audio from a text description. Use this as the first step in [voice generation](/docs-and-guides/voices/voice-generation).
After choosing a preview, call [Finalize voice design](/api-reference/voices/finalize-design) to save the voice.
# Finalize voice design
Source: https://docs.beyondwords.io/api-reference/voices/finalize-design
post /organization/voices/finalize_design
Create a permanent voice from a generated design preview
Create a permanent voice from a generated design preview. Use this as the second step in [voice generation](/docs-and-guides/voices/voice-generation), after [Design voice previews](/api-reference/voices/design-previews).
# List voices
Source: https://docs.beyondwords.io/api-reference/voices/list
get /organization/voices
Lists all voices available to your organization.
# Get voice
Source: https://docs.beyondwords.io/api-reference/voices/show
get /organization/voices/{id}
Returns a voice available to your organization.
# Update voice
Source: https://docs.beyondwords.io/api-reference/voices/update
put /organization/voices/{id}
Updates a voice in your organization.
# Create webhook
Source: https://docs.beyondwords.io/api-reference/webhooks/create
post /projects/{project_id}/webhooks
Creates a webhook for a project to receive HTTP notifications when content changes.
Deliveries use the `audio.updated` action type. See `#/components/examples/audioUpdatedWebhookDeliveryExample`
for a representative payload (media IDs are integers, `content_type` values are `mp3`/`m3u8`/`mp4`,
and `duration` is in seconds).
# Delete webhook
Source: https://docs.beyondwords.io/api-reference/webhooks/delete
delete /projects/{project_id}/webhooks/{id}
Deletes a webhook from a project.
# List webhooks
Source: https://docs.beyondwords.io/api-reference/webhooks/list
get /projects/{project_id}/webhooks
Lists webhooks configured for a project.
# Get webhook
Source: https://docs.beyondwords.io/api-reference/webhooks/show
get /projects/{project_id}/webhooks/{id}
Returns a single webhook by ID.
# Test webhook
Source: https://docs.beyondwords.io/api-reference/webhooks/test
post /projects/{project_id}/webhooks/test
Sends a test payload to a project webhook URL to verify delivery.
# Update webhook
Source: https://docs.beyondwords.io/api-reference/webhooks/update
put /projects/{project_id}/webhooks/{id}
Updates a webhook for a project.
# Advert Link
Source: https://docs.beyondwords.io/components/html/advert-link
Links to the current advert's click-through URL.
```js AdvertLink.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
const getHostName = (url) => {
try {
return new URL(url).hostname.replace(/^www\./, "");
} catch {
return null;
}
};
export class BwAdvertLink extends BwPlayerConsumer {
_link = null;
_listener = null;
constructor() {
super();
this._link = document.createElement("a");
this._link.setAttribute("target", "_blank");
this._link.setAttribute("rel", "noreferrer");
this._link.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedAdvertLink",
description: "The advert link was pressed to open the click-through URL in a new tab.",
initiatedBy: "user",
}),
);
});
}
#render() {
const advert = this._player?.adverts?.[this._player.advertIndex];
if (!advert) {
this._link.remove();
return;
}
this._link.setAttribute("href", advert.clickThroughUrl);
this._link.textContent = getHostName(advert.clickThroughUrl) ?? "";
if (!this._link.isConnected) this.appendChild(this._link);
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => this.#render());
this.#render();
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-advert-link", BwAdvertLink);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add advert-link --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add advert-link --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add advert-link --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add advert-link --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the link inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor, reads the
current advert (`adverts[advertIndex]`) from it, and renders a link to its
`clickThroughUrl` (showing the hostname as the label), updating on every
player event. On click it emits a `PressedAdvertLink` event (built with
[`newEvent`](/components/html/new-event)). It renders nothing when there's no
active advert, so it's safe to include unconditionally.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `adverts`, `advertIndex` |
| **Events emitted** | `PressedAdvertLink` |
| **Requires** | a `` ancestor and the player UMD global loaded |
# BeyondWords Logo
Source: https://docs.beyondwords.io/components/html/beyondwords-logo
A BeyondWords logo that links back to beyondwords.io with analytics attribution.
```js BeyondWordsLogo.js theme={null}
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
const LOGO_SVG = ``;
export class BwBeyondWordsLogo extends BwPlayerConsumer {
_link = null;
_listener = null;
constructor() {
super();
this._link = document.createElement("a");
this._link.target = "_blank";
this._link.rel = "noreferrer";
this._link.style.display = "inline-flex";
this._link.style.cursor = "pointer";
this._link.innerHTML = LOGO_SVG;
}
#render() {
const analyticsId = this._player?.analyticsId ?? "";
this._link.href = `https://beyondwords.io/?utm_source=${encodeURIComponent(window.location.origin)}&utm_medium=player&utm_campaign=${analyticsId}`;
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => this.#render());
this.#render();
this.appendChild(this._link);
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-beyondwords-logo", BwBeyondWordsLogo);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add beyondwords-logo --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add beyondwords-logo --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add beyondwords-logo --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add beyondwords-logo --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the element inside a ``.
```html theme={null}
```
`` renders the BeyondWords logo as a link to beyondwords.io,
tagged with UTM parameters and the player's `analyticsId` (read from the nearest
[``](/components/html/player-provider) ancestor). The SVG uses
`currentColor`, so it inherits the surrounding text color — style it by setting
the color on a parent.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `analyticsId` |
| **Events emitted** | None |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Call To Action
Source: https://docs.beyondwords.io/components/html/call-to-action
Displays the player's call-to-action text, with a fallback when none is set.
```js CallToAction.js theme={null}
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwCallToAction extends BwPlayerConsumer {
_listener = null;
#render() {
this.textContent = this._player?.callToAction ?? "Listen to this article";
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => this.#render());
this.#render();
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-call-to-action", BwCallToAction);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add call-to-action --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add call-to-action --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add call-to-action --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add call-to-action --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the element inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor and reads
`callToAction` from it, updating on every player event. When the source has no
call-to-action configured, it falls back to `"Listen to this article"`. Edit
the fallback or the markup to match your UI.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `callToAction` |
| **Events emitted** | None (display only) |
| **Requires** | a `` ancestor and the player UMD global loaded |
# Content Title
Source: https://docs.beyondwords.io/components/html/content-title
Displays the title of the current content item.
```js ContentTitle.js theme={null}
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwContentTitle extends BwPlayerConsumer {
_listener = null;
#render() {
this.textContent = this._player?.content?.[this._player.contentIndex]?.title ?? "";
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => this.#render());
this.#render();
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-content-title", BwContentTitle);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add content-title --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add content-title --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add content-title --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add content-title --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the title inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor and reads
`content` and `contentIndex` from it, updating on every player event. It
renders the current item's title (`content[contentIndex].title`) as its text
content.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `content`, `contentIndex` |
| **Events emitted** | None (display only) |
| **Requires** | a `` ancestor and the player UMD global loaded |
# Download Button
Source: https://docs.beyondwords.io/components/html/download-button
Downloads the current item's audio as an MP3.
```js DownloadButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { downloadSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwDownloadButton extends BwPlayerConsumer {
_btn = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.setAttribute("aria-label", "Download audio");
this._btn.innerHTML = downloadSvg(20);
this._btn.addEventListener("click", () => {
if (!this._player) return;
const content = this._player.content;
const contentIndex = this._player.contentIndex;
const audioIndex = content[contentIndex]?.audio?.findIndex((a) => a.url?.endsWith(".mp3"));
if (typeof audioIndex !== "number" || audioIndex === -1) return;
this._player.onEvent(
newEvent({
type: "PressedDownload",
description: "The download button was pressed.",
contentIndex,
audioIndex,
}),
);
});
}
connectedCallback() {
super.connectedCallback();
this.appendChild(this._btn);
}
}
customElements.define("bw-download-button", BwDownloadButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add download-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add download-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add download-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add download-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. On click,
it finds the current item's MP3 audio and emits a `PressedDownload` event
(built with [`newEvent`](/components/html/new-event)) carrying the `contentIndex`
and `audioIndex`. If the current item has no MP3, the click is a no-op.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `content`, `contentIndex` |
| **Events emitted** | `PressedDownload` |
| **Requires** | a `` ancestor and the player UMD global loaded |
# Feature Image
Source: https://docs.beyondwords.io/components/html/feature-image
Displays the feature image of the current content item.
```js FeatureImage.js theme={null}
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwFeatureImage extends BwPlayerConsumer {
_img = null;
_listener = null;
constructor() {
super();
this._img = document.createElement("img");
this._img.style.maxWidth = "100%";
this._img.style.objectFit = "contain";
}
#render() {
const item = this._player?.content?.[this._player.contentIndex];
if (!item?.imageUrl) {
this._img.remove();
return;
}
if (this._img.getAttribute("src") !== item.imageUrl) this._img.setAttribute("src", item.imageUrl);
this._img.setAttribute("alt", item.title ?? "");
if (!this._img.isConnected) this.appendChild(this._img);
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => this.#render());
this.#render();
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-feature-image", BwFeatureImage);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add feature-image --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add feature-image --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add feature-image --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add feature-image --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the image inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor, reads `content`
and `contentIndex` from it, and renders the current item's `imageUrl` as an
`` (using its `title` as the `alt` text), updating on every player event.
It renders nothing when the item has no image, so it's safe to drop in
unconditionally.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `content`, `contentIndex` |
| **Events emitted** | None (display only) |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Introduction
Source: https://docs.beyondwords.io/components/html/introduction
Composable, minimal custom elements for the BeyondWords player. Own the code and style it your way.
A set of framework-agnostic [custom elements](https://developer.mozilla.org/docs/Web/Web_Components)
for building your own UI on top of the
[BeyondWords player](https://github.com/beyondwords-io/player). They give you
play/pause, seeking, playback rate and more as small building blocks you compose
in plain HTML and style yourself. The player keeps doing the hard part:
playback, analytics, ads, segments and lock-screen integration.
This is not a packaged widget. The elements are distributed as a
[shadcn registry](https://ui.shadcn.com/docs/registry), so **you own the code**:
the CLI copies the source straight into your project, where you can restyle,
rearrange or rewrite it.
```bash npm theme={null}
npx @beyondwords/cli components add play-pause-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add play-pause-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add play-pause-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add play-pause-button --html
```
Here's the [Standard Player](/components/html/standard-player) example, running
live, built entirely from these elements:
Using React? Everything here is also available as
[React components](/components/react/introduction). Use the React / HTML tabs
at the top of the Components section.
## How it works
The kit is three layers on top of one engine. Stop at whichever layer
gives you the control you need:
1. **[Player Provider](/components/html/player-provider)**:
`` mounts the player JavaScript SDK (loaded as the
global `BeyondWords`), holds the instance, and shares it with the elements
inside. This is the foundation everything else sits on.
2. **Components**: small, minimal custom elements like
[Play Pause Button](/components/html/play-pause-button) and
[Progress Bar](/components/html/progress-bar). You bring the styling.
3. **Examples**: complete, pre-assembled players you drop in and tweak, like
the [Standard Player](/components/html/standard-player).
Every element behaves the same way:
* **It reads state from the player instance** by extending `BwPlayerConsumer`,
which resolves the nearest provider and exposes the instance as
`this._player`. Elements stay in sync by listening for player events and
updating their own markup.
* **It drives the player by emitting events** built with
[`newEvent`](/components/html/new-event). This is the same path the standard
player UI uses, so analytics, ads and segment logic stay correct. Simple
state such as `playbackRate` can also be set directly on the instance.
Every layer is copied into your codebase, so there's no black box and no
version lock-in. The trade-off is that you maintain what you customize.
## Next steps
Install the elements and build your first player.Browse the minimal building blocks.Restyle the elements to match your brand.Drop in a complete, pre-built player.
# Minimal Player
Source: https://docs.beyondwords.io/components/html/minimal-player
A transparent, ultra-minimal player that expands from play + call to action into rate, seek-back, and time controls.
```html MinimalPlayer.html theme={null}
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add minimal-player --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add minimal-player --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add minimal-player --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add minimal-player --html
```
## Usage
Unlike the React block, there is no single `` element. Compose
the custom elements inside a [``](/components/html/player-provider):
```html theme={null}
```
While stopped, only play and the "Listen to article" call to action are visible.
Once playback starts, seek-back 10s, the speed dropdown (0.9x–2x), and current
time appear instead. Every element resolves its nearest
[``](/components/html/player-provider) ancestor and
subscribes to player events; the buttons emit events built with
[`newEvent`](/components/html/new-event).
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
# New Event
Source: https://docs.beyondwords.io/components/html/new-event
Utility for creating well-formed player event payloads.
Stamps the common fields (`id`, `createdAt`, `status`, `initiatedBy`) onto a
player event payload so every event you emit is well-formed. It's plain JS,
identical across frameworks.
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add new-event --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add new-event --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add new-event --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add new-event --html
```
## Usage
```js theme={null}
import { newEvent } from "@/lib/newEvent";
player.onEvent(
newEvent({
type: "PressedPlay",
description: "The play button was pressed.",
}),
);
// => { id, createdAt, status: 'pending', initiatedBy: 'user', type, description }
```
# Next Seconds Button
Source: https://docs.beyondwords.io/components/html/next-seconds-button
Seeks forward by a fixed number of seconds.
```js NextSecondsButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { seekForwardSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwNextSecondsButton extends BwPlayerConsumer {
_btn = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedSeekAhead",
description: "The seek ahead button was pressed.",
seconds: this._seconds,
}),
);
});
}
get _seconds() {
const seconds = Number(this.dataset.seconds);
return Number.isFinite(seconds) && seconds > 0 ? seconds : 10;
}
connectedCallback() {
super.connectedCallback();
this._btn.setAttribute("aria-label", `Skip forward ${this._seconds} seconds`);
this._btn.innerHTML = seekForwardSvg(this._seconds, 24);
this.appendChild(this._btn);
}
}
customElements.define("bw-next-seconds-button", BwNextSecondsButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add next-seconds-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add next-seconds-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add next-seconds-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add next-seconds-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. On click,
it emits a `PressedSeekAhead` event (built with
[`newEvent`](/components/html/new-event)) carrying the number of `seconds` to seek.
The `data-seconds` attribute controls both the jump distance and the label,
and defaults to `10`.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------ |
| **Attributes** | `data-seconds` (default `10`) |
| **Player state read** | None |
| **Events emitted** | `PressedSeekAhead` |
| **Requires** | a `` ancestor and the player UMD global loaded |
# Next Segment Button
Source: https://docs.beyondwords.io/components/html/next-segment-button
Jumps to the next segment and emits a user interaction event.
```js NextSegmentButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { nextSegmentSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwNextSegmentButton extends BwPlayerConsumer {
_btn = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedNextSegment",
description: "The next segment button was pressed.",
}),
);
});
}
connectedCallback() {
super.connectedCallback();
this._btn.setAttribute("aria-label", "Next segment");
this._btn.innerHTML = nextSegmentSvg(24);
this.appendChild(this._btn);
}
}
customElements.define("bw-next-segment-button", BwNextSegmentButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add next-segment-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add next-segment-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add next-segment-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add next-segment-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. On click,
it emits a `PressedNextSegment` event built with
[`newEvent`](/components/html/new-event). The player handles the event and moves
playback to the next segment.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | None |
| **Events emitted** | `PressedNextSegment` |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Next Track Button
Source: https://docs.beyondwords.io/components/html/next-track-button
Skips to the next track in a playlist.
```js NextTrackButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { nextTrackSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwNextTrackButton extends BwPlayerConsumer {
_btn = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedNextTrack",
description: "The next track button was pressed.",
}),
);
});
}
connectedCallback() {
super.connectedCallback();
this._btn.setAttribute("aria-label", "Next track");
this._btn.innerHTML = nextTrackSvg(24);
this.appendChild(this._btn);
}
}
customElements.define("bw-next-track-button", BwNextTrackButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add next-track-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add next-track-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add next-track-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add next-track-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. On click,
it emits a `PressedNextTrack` event built with
[`newEvent`](/components/html/new-event). It's intended for playlist sources,
where the player moves to the next track.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | None |
| **Events emitted** | `PressedNextTrack` |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Play Pause Button
Source: https://docs.beyondwords.io/components/html/play-pause-button
Toggles playback and emits user interaction events.
```js PlayPauseButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { pauseCircleSvg, playCircleSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwPlayPauseButton extends BwPlayerConsumer {
_btn = null;
_listener = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
const name = this._player.playbackState === "playing" ? "Pause" : "Play";
this._player.onEvent(
newEvent({
type: `Pressed${name}`,
description: `The ${name.toLowerCase()} button was pressed.`,
}),
);
});
}
#render() {
const isPlaying = this._player?.playbackState === "playing";
this._btn.setAttribute("aria-label", isPlaying ? "Pause audio" : "Play audio");
this._btn.innerHTML = isPlaying ? pauseCircleSvg(40) : playCircleSvg(40);
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => this.#render());
this.#render();
this.appendChild(this._btn);
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-play-pause-button", BwPlayPauseButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add play-pause-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add play-pause-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add play-pause-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add play-pause-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
The button reads `playbackState` from the surrounding
[``](/components/html/player-provider). On
click, it emits a `PressedPlay` / `PressedPause` event built with
[`newEvent`](/components/html/new-event).
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `playbackState` |
| **Events emitted** | `PressedPlay`, `PressedPause` |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Playback Rate Button
Source: https://docs.beyondwords.io/components/html/playback-rate-button
Toggles the player's playback speed between 1x and 1.5x.
```js PlaybackRateButton.js theme={null}
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwPlaybackRateButton extends BwPlayerConsumer {
_btn = null;
_listener = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
this._player.playbackRate = this._player.playbackRate === 1 ? 1.5 : 1;
});
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => {
this._btn.textContent = `${this._player.playbackRate}x`;
});
this._btn.textContent = `${this._player?.playbackRate}x`;
this.appendChild(this._btn);
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-playback-rate-button", BwPlaybackRateButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add playback-rate-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add playback-rate-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add playback-rate-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add playback-rate-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor and reads
`playbackRate` from it, updating on every player event. On click, it sets
`player.playbackRate` directly on the instance, toggling between `1` and
`1.5`. The current rate renders as the label (e.g. `1x`). Adjust the toggle
logic to expose whatever speeds you want.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `playbackRate` |
| **Events emitted** | None. Sets `player.playbackRate` directly |
| **Requires** | a `` ancestor and the player UMD global loaded |
# Player Provider
Source: https://docs.beyondwords.io/components/html/player-provider
Custom elements for providing and consuming the BeyondWords player instance.
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add player-provider --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add player-provider --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add player-provider --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add player-provider --html
```
## Usage
`` creates a `BeyondWords.Player` and exposes it to
descendant elements. Extend `BwPlayerConsumer` to read it.
```html theme={null}
```
### API
* **``** creates the player from its `data-*` attributes (`data-project-id`, `data-content-id`, `data-source-id`, `data-playlist-id`, `data-player-style`, `data-show-user-interface`, plus inline `data-content`, `data-adverts`, `data-advert-index` and `data-analytics-consent`) and destroys it on disconnect.
* **`BwPlayerConsumer`** is the base class. It walks up the DOM to find its provider and exposes the player via the `_player` getter and the widget mode via `_mode`. Throws if no `` ancestor exists.
# Playlist
Source: https://docs.beyondwords.io/components/html/playlist
Render the player's content list and let users jump between items.
```js Playlist.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { nowPlayingMarkerSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwPlaylist extends BwPlayerConsumer {
_list = null;
_listener = null;
_content = null;
_contentIndex = null;
constructor() {
super();
this._list = document.createElement("ul");
this._list.style.listStyle = "none";
this._list.style.margin = "0";
this._list.style.padding = "0";
}
#render() {
const content = this._player?.content ?? [];
const contentIndex = this._player?.contentIndex;
if (content === this._content && contentIndex === this._contentIndex) return;
this._content = content;
this._contentIndex = contentIndex;
this._list.replaceChildren(
...content.map((item, index) => {
const btn = document.createElement("button");
btn.setAttribute("aria-current", contentIndex === index);
btn.setAttribute("aria-label", `Play ${item.title}`);
btn.style.display = "flex";
btn.style.alignItems = "center";
btn.style.gap = "0.5rem";
btn.style.width = "100%";
btn.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedPlaylistItem",
description: "A playlist item was pressed.",
index,
}),
);
});
const marker = document.createElement("span");
marker.style.display = "inline-flex";
marker.style.width = "1rem";
marker.style.justifyContent = "center";
if (contentIndex === index) {
marker.innerHTML = nowPlayingMarkerSvg(16);
} else {
marker.textContent = index + 1;
}
const title = document.createElement("span");
title.textContent = item.title;
btn.append(marker, title);
const li = document.createElement("li");
li.appendChild(btn);
return li;
}),
);
}
connectedCallback() {
super.connectedCallback();
this._listener = this._player?.addEventListener("", () => this.#render());
this.#render();
this.appendChild(this._list);
}
disconnectedCallback() {
this._player?.removeEventListener("", this._listener);
this._listener = null;
super.disconnectedCallback();
}
}
customElements.define("bw-playlist", BwPlaylist);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add playlist --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add playlist --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add playlist --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add playlist --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the playlist inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor and maps over
its `content`. It renders a button per item and marks the active one
(`contentIndex`) with a now-playing icon. Pressing an item emits a `PressedPlaylistItem`
event (built with [`newEvent`](/components/html/new-event)) carrying its `index`.
The event is emitted rather than setting `player.contentIndex` directly. This
lets the player handle ads and outros correctly when switching tracks.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | `content`, `contentIndex` |
| **Events emitted** | `PressedPlaylistItem` (carries the item's `index`) |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Playlist Player
Source: https://docs.beyondwords.io/components/html/playlist-player
A playlist player example with a scrollable track list alongside playback controls.
```html PlaylistPlayer.html theme={null}
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add playlist-player --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add playlist-player --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add playlist-player --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add playlist-player --html
```
## Usage
Compose the playlist custom elements inside a
[``](/components/html/player-provider). Import the modules
(each calls `customElements.define`), then arrange the elements:
```html theme={null}
```
Unlike the React block, there is no single `` element. Pass
`data-project-id` and `data-playlist-id` instead of a single `data-source-id`.
`` (call-to-action + duration) shows while stopped, and
`` (playback rate, previous/next track, time, and
progress) shows once playback starts. `` shows or hides
the scrollable `` of the playlist's items. Every element resolves
its nearest [``](/components/html/player-provider)
ancestor and subscribes to player events; the buttons emit events built with
[`newEvent`](/components/html/new-event).
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
# Previous Seconds Button
Source: https://docs.beyondwords.io/components/html/prev-seconds-button
Seeks backward by a fixed number of seconds.
```js PrevSecondsButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { seekBackSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwPrevSecondsButton extends BwPlayerConsumer {
_btn = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedSeekBack",
description: "The seek backward button was pressed.",
seconds: this._seconds,
}),
);
});
}
get _seconds() {
const seconds = Number(this.dataset.seconds);
return Number.isFinite(seconds) && seconds > 0 ? seconds : 10;
}
connectedCallback() {
super.connectedCallback();
this._btn.setAttribute("aria-label", `Skip back ${this._seconds} seconds`);
this._btn.innerHTML = seekBackSvg(this._seconds, 24);
this.appendChild(this._btn);
}
}
customElements.define("bw-prev-seconds-button", BwPrevSecondsButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add prev-seconds-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add prev-seconds-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add prev-seconds-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add prev-seconds-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. On click,
it emits a `PressedSeekBack` event (built with
[`newEvent`](/components/html/new-event)) carrying the number of `seconds` to seek.
The `data-seconds` attribute controls both the jump distance and the label,
and defaults to `10`.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------ |
| **Attributes** | `data-seconds` (default `10`) |
| **Player state read** | None |
| **Events emitted** | `PressedSeekBack` |
| **Requires** | a `` ancestor and the player UMD global loaded |
# Previous Segment Button
Source: https://docs.beyondwords.io/components/html/prev-segment-button
Jumps to the previous segment and emits a user interaction event.
```js PrevSegmentButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { previousSegmentSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwPrevSegmentButton extends BwPlayerConsumer {
_btn = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedPrevSegment",
description: "The previous segment button was pressed.",
}),
);
});
}
connectedCallback() {
super.connectedCallback();
this._btn.setAttribute("aria-label", "Previous segment");
this._btn.innerHTML = previousSegmentSvg(24);
this.appendChild(this._btn);
}
}
customElements.define("bw-prev-segment-button", BwPrevSegmentButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add prev-segment-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add prev-segment-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add prev-segment-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add prev-segment-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. On click,
it emits a `PressedPrevSegment` event built with
[`newEvent`](/components/html/new-event). The player handles the event and moves
playback to the previous segment.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | None |
| **Events emitted** | `PressedPrevSegment` |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Previous Track Button
Source: https://docs.beyondwords.io/components/html/prev-track-button
Skips to the previous track in a playlist.
```js PrevTrackButton.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { previousTrackSvg } from "@/registry/html/icons";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwPrevTrackButton extends BwPlayerConsumer {
_btn = null;
constructor() {
super();
this._btn = document.createElement("button");
this._btn.addEventListener("click", () => {
if (!this._player) return;
this._player.onEvent(
newEvent({
type: "PressedPrevTrack",
description: "The previous track button was pressed.",
}),
);
});
}
connectedCallback() {
super.connectedCallback();
this._btn.setAttribute("aria-label", "Previous track");
this._btn.innerHTML = previousTrackSvg(24);
this.appendChild(this._btn);
}
}
customElements.define("bw-prev-track-button", BwPrevTrackButton);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add prev-track-button --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add prev-track-button --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add prev-track-button --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add prev-track-button --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. On click,
it emits a `PressedPrevTrack` event built with
[`newEvent`](/components/html/new-event). It's intended for playlist sources,
where the player moves to the previous track.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | ------------------------------------------------------------------------------------------------------ |
| **Attributes** | None |
| **Player state read** | None |
| **Events emitted** | `PressedPrevTrack` |
| **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded |
# Progress Bar
Source: https://docs.beyondwords.io/components/html/progress-bar
A seekable progress bar that reflects playback and emits scrub events.
```js ProgressBar.js theme={null}
import { newEvent } from "@/lib/newEvent";
import { BwPlayerConsumer } from "@/registry/html/PlayerProvider";
export class BwProgressBar extends BwPlayerConsumer {
_background = null;
_fill = null;
_listener = null;
_scrubbing = false;
constructor() {
super();
// A custom element's constructor must not touch the host (no attributes,
// styles, or children) — only build detached nodes and bind handlers here.
this._background = document.createElement("div");
this._background.style.position = "absolute";
this._background.style.inset = "0";
this._background.style.cursor = "pointer";
this._background.style.backgroundColor = "rgba(0, 0, 0, 0.8)";
this._background.style.opacity = "0.15";
this._fill = document.createElement("div");
this._fill.style.height = "0.5rem";
this._fill.style.width = "0%";
this._fill.style.borderRadius = "0.25rem";
this._fill.style.backgroundColor = "rgba(0, 0, 0, 0.8)";
this._fill.style.pointerEvents = "none";
this._onPress = this._onPress.bind(this);
this._onMove = this._onMove.bind(this);
this._onRelease = this._onRelease.bind(this);
}
connectedCallback() {
super.connectedCallback();
this.style.position = "relative";
this.style.display = "block";
this.style.isolation = "isolate";
this.style.height = "0.5rem";
this.style.flexGrow = "1";
this.style.overflow = "hidden";
this.style.borderRadius = "0.25rem";
this.style.cursor = "pointer";
this.appendChild(this._background);
this.appendChild(this._fill);
this.addEventListener("mousedown", this._onPress);
this.addEventListener("touchstart", this._onPress);
// Track drags on the window so scrubbing continues outside the bar.
window.addEventListener("mousemove", this._onMove);
window.addEventListener("mouseup", this._onRelease);
window.addEventListener("touchmove", this._onMove);
window.addEventListener("touchend", this._onRelease);
this._listener = this._player?.addEventListener("", () => this._update());
this._update();
}
_ratio(event) {
const clientX = event.clientX ?? event.touches?.[0]?.clientX ?? 0;
const { x, width } = this.getBoundingClientRect();
return Math.max(0, Math.min(1, (clientX - x) / width));
}
_onPress(event) {
if (!this._player) return;
this._scrubbing = true;
this._player.onEvent(
newEvent({
type: "PressedProgressBar",
description: "The progress bar was pressed at some ratio.",
initiatedBy: "user",
ratio: this._ratio(event),
}),
);
}
_onMove(event) {
if (!this._scrubbing || !this._player) return;
this._player.onEvent(
newEvent({
type: "ScrubbedProgressBar",
description: "The user pressed on the progress bar then dragged.",
initiatedBy: "user",
ratio: this._ratio(event),
}),
);
}
_onRelease() {
if (!this._scrubbing || !this._player) return;
this._scrubbing = false;
this._player.onEvent(
newEvent({
type: "FinishedScrubbingProgressBar",
description: "The user let go after scrubbing the progress bar.",
initiatedBy: "user",
}),
);
}
_update() {
const currentTime = this._player?.currentTime ?? 0;
const duration = this._player?.duration ?? 0;
const progress = duration > 0 ? Math.max(0, Math.min(currentTime / duration, 1)) : 0;
this._fill.style.width = `${progress * 100}%`;
}
disconnectedCallback() {
this.removeEventListener("mousedown", this._onPress);
this.removeEventListener("touchstart", this._onPress);
window.removeEventListener("mousemove", this._onMove);
window.removeEventListener("mouseup", this._onRelease);
window.removeEventListener("touchmove", this._onMove);
window.removeEventListener("touchend", this._onRelease);
this._player?.removeEventListener("", this._listener);
this._listener = null;
this._scrubbing = false;
super.disconnectedCallback();
}
}
customElements.define("bw-progress-bar", BwProgressBar);
```
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add progress-bar --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add progress-bar --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add progress-bar --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add progress-bar --html
```
## Usage
Import the modules (each calls `customElements.define`), then use the elements. Nest the bar inside a ``.
```html theme={null}
```
`` finds its nearest
[``](/components/html/player-provider) ancestor. The bar fills to
`currentTime / duration`. Pressing and dragging emits a sequence of events
built with [`newEvent`](/components/html/new-event). Each one carries the pressed
`ratio` (0–1):
* **`PressedProgressBar`**: on press, to seek to a position.
* **`ScrubbedProgressBar`**: while dragging.
* **`FinishedScrubbingProgressBar`**: on release.
Drag tracking is attached to `window` (mouse and touch), so scrubbing keeps
working even when the pointer leaves the bar. The element grows to fill its
container (`flex-grow: 1`). Place it in a flex row alongside the other
controls.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
## Reference
| | |
| --------------------- | --------------------------------------------------------------------------------------------------------- |
| **Attributes** | None |
| **Player state read** | `currentTime`, `duration` |
| **Events emitted** | `PressedProgressBar`, `ScrubbedProgressBar`, `FinishedScrubbingProgressBar` (each carries a `ratio`, 0–1) |
| **Requires** | a `` ancestor and the player UMD global loaded |
# Setup
Source: https://docs.beyondwords.io/components/html/setup
Install the custom elements with the BeyondWords CLI and build your first player.
The elements ship as a shadcn registry. Add them with the CLI and the source is
copied into your project. They also need the BeyondWords player script on the
page.
## Prerequisites
* A project where you can run `npx` commands and serve ES modules. The CLI
copies the element source into your project.
* A BeyondWords **project ID** and a **source ID** (or content ID). The examples
below use a public demo project.
## Load the player script
The elements expect the BeyondWords player as a global `BeyondWords`. Add the
player UMD script to your page so it loads before the provider connects:
```html theme={null}
```
## Add a component
Add the [Play Pause Button](/components/html/play-pause-button). No configuration
is needed. The `--html` flag selects the HTML registry, and the CLI resolves
dependencies automatically, so this also pulls in
[Player Provider](/components/html/player-provider) and
[New Event](/components/html/new-event).
```bash theme={null}
npx @beyondwords/cli components add play-pause-button --html
```
The CLI copies the files to `components/beyondwords/` by default (it asks
first), or respects your aliases if the project has a `components.json`. The
examples in these docs import from `@/registry/html/`. Adjust the paths to
wherever the files landed, and serve them through a bundler or an import map so
the browser can resolve them.
## Build your first player
Each module calls `customElements.define`, so importing it registers the tag.
Nest your controls inside a ``, which mounts the player and
shares it with the elements inside. `data-show-user-interface="false"` hides the
player's built-in UI so yours is the only one on the page.
```html theme={null}
```
Here's that button running live:
It looks plain because the elements are minimal by design;
[Theming](/components/html/theming) shows how to make it yours. Add more
controls the same way: import the module, nest the element inside the provider.
To build your own controls, extend `BwPlayerConsumer`, which resolves the
player instance from the nearest ``. See
[Player Provider](/components/html/player-provider) for the pattern, and
[New Event](/components/html/new-event) for emitting player events.
## Using the shadcn CLI instead (optional)
The registry also works with the shadcn CLI directly. Add components by full
URL:
```bash theme={null}
npx shadcn add https://ui.beyondwords.io/r/html/play-pause-button.json
```
Or register the registry namespace once in your `components.json`, then use
short names:
```json components.json theme={null}
{
"registries": {
"@beyondwords-html": "https://ui.beyondwords.io/r/html/{name}.json"
}
}
```
```bash theme={null}
npx shadcn add @beyondwords-html/play-pause-button
```
## Next steps
Style the elements to match your brand.Provide and consume the player.Start from a complete, pre-built player.Fix common setup issues.
# Standard Player
Source: https://docs.beyondwords.io/components/html/standard-player
A full-featured player block with playback controls, a call to action, and duration.
```html StandardPlayer.html theme={null}
```
## Try it
An editable example running on [StackBlitz](https://stackblitz.com/edit/html-standard-player?file=index.html):
## Installation
```bash npm theme={null}
npx @beyondwords/cli components add standard-player --html
```
```bash pnpm theme={null}
pnpm dlx @beyondwords/cli components add standard-player --html
```
```bash yarn theme={null}
yarn dlx @beyondwords/cli components add standard-player --html
```
```bash bun theme={null}
bunx --bun @beyondwords/cli components add standard-player --html
```
## Usage
Unlike the React block, there is no single `` element. The
block is a set of custom elements you compose yourself inside a
[``](/components/html/player-provider). Import the modules
(each calls `customElements.define`), then arrange the elements:
```html theme={null}
```
The pieces toggle with playback state: `` (call to action +
duration) shows while stopped, and `` shows once playback
starts. Every element resolves its nearest
[``](/components/html/player-provider) ancestor and
subscribes to player events; the buttons emit events built with
[`newEvent`](/components/html/new-event).
`` is the horizontal bar that holds the controls. The
player also detaches into a floating widget once scrolled out of view:
`` watches its own visibility (via
`IntersectionObserver`) and the playback state, exposing a `mode` to its
descendants. While playing and scrolled out of view, the mode switches from
`"inline"` to `"widget"`: the bar pins itself to the bottom-right corner and
`` appears — pressing it pins the player back
inline permanently.
These components expect the BeyondWords player to be available as a global
`BeyondWords` (the player UMD script). Load it before the provider connects.
# Theming
Source: https://docs.beyondwords.io/components/html/theming
Style the custom elements to match your brand with plain CSS or by editing the code you own.
The elements are deliberately minimal and unopinionated. There's no `theme`
attribute and no set of CSS variables to override. You have two ways to make
them yours. **Style them with ordinary CSS** from your page, or **edit the
source** the CLI copied into your project.
## Style with CSS
The elements render into the light DOM (no shadow root), so your page's CSS
reaches their internals directly, with no source edits required. Target the
element or the `