-
Notifications
You must be signed in to change notification settings - Fork 0
Component Types
Every conversion produces Component[]. This page lists the component union, the shared shape, the full field reference for each type, and the type guards used to narrow them. All of these are defined in component.ts and re-exported from the package root.
← Back to Home · Related: HTML Mapping · API Reference
Every component extends a common base:
type Component = {
id?: string;
component: ComponentType;
properties?: Record<string, unknown>;
html?: string;
errors: readonly FeedIssue[];
warnings: readonly FeedIssue[];
element?: { tag: string; attributes?: Record<string, string> };
};ComponentLink is a resolved link carried directly on a component rather than serialised into its content. It is set when an enclosing <a> ancestor is resolved during mapping.
type ComponentLink = {
href: string;
element?: { tag: string; attributes?: Record<string, string> };
};href is the anchor's destination URL. element records the source <a> element (tag name and attributes) that the href was taken from, allowing consumers to inspect or re-render the original anchor.
ComponentLink is exported from the package root alongside the component types.
ComponentType is the union of every component kind. It includes TextType plus the structural/media kinds:
-
Text (
TextType):headline,title,subtitle,intro,body,crosshead,byline,blockquote,footer,imagecaption, ortext1–text60. -
Media / embed:
image,gallery,video,audio,twitter,instagram,tiktok,infogram. -
Structural:
container,columns,live_container,live_post,htmltable,recipe,custom,button,anchor,advert,spacer,divider,map,table.
MAX_TEXT is 60. The text1–text60 range is generated at the type level, backed by a runtime Set for validation.
component: any TextType value — headline, title, subtitle, intro, body, crosshead, byline, blockquote, footer, imagecaption, or text1–text60.
| Field | Type | Description |
|---|---|---|
text |
string |
Sanitized inner HTML of the element. Inline phrasing tags (<a>, <strong>, <em>, <abbr>, <cite>, <u>, <time>, etc.) are preserved; block elements and disallowed attributes are stripped. |
link |
ComponentLink (optional)
|
Present when the element was wrapped in an <a> ancestor during mapping. The text content itself is not wrapped in an anchor tag — read the link from this field instead. |
Default HTML → component mappings:
-
h1→headline -
h2→title -
h3→subtitle -
h4→intro -
h5→crosshead -
h6→byline -
p,ol,ul,a→body -
blockquote→blockquote -
footer→footer
The role attribute on any element overrides the mapping (e.g. <p role="crosshead"> → crosshead). <hr> and <br> follow the same idea outside the text-component family — see DividerComponent/SpacerComponent below.
{
"component": "body",
"text": "The <strong>quick</strong> brown fox.",
"errors": [],
"warnings": [],
"element": { "tag": "p" }
}With a resolved link:
{
"component": "body",
"text": "Read the full report.",
"link": {
"href": "https://example.com/report",
"element": {
"tag": "a",
"attributes": { "href": "https://example.com/report", "target": "_blank" }
}
},
"errors": [],
"warnings": [],
"element": { "tag": "p" }
}component: "image"
| Field | Type | Description |
|---|---|---|
imageurl |
string |
Source URL of the image (the <img>'s src, or a <picture>'s fallback <img> src). |
link |
string (optional)
|
URL the image links to. |
alt |
string (optional)
|
Alt text. |
caption |
string (optional)
|
Caption text. |
credit |
string (optional)
|
Credit / attribution text. |
width |
number (optional)
|
Intrinsic width in pixels. |
height |
number (optional)
|
Intrinsic height in pixels. |
srcset |
string (optional)
|
The <img>'s (or <picture>'s fallback <img>'s) srcset attribute, verbatim. |
sources |
ImageSource[] (optional)
|
Only set for a <picture>: one entry per <source> child that has a srcset, in document order (see below). |
ImageSource — one <picture> <source> candidate:
| Field | Type | Description |
|---|---|---|
srcset |
string |
The <source>'s srcset attribute. |
media |
string (optional)
|
The <source>'s media attribute (a media query). |
type |
string (optional)
|
The <source>'s type attribute (a MIME type). |
sizes |
string (optional)
|
The <source>'s sizes attribute. |
Note: A
<figure>wrapping an<img>or<picture>produces aFigureContainerComponent(see Transient components) that holds theImageComponentas a child. A bare<img>outside a<figure>produces a standaloneImageComponent. A<picture>with more than one<img>child records aDUPLICATE_IMG_TAGwarning and keeps only the first.
{
"component": "image",
"imageurl": "https://example.com/photo.jpg",
"alt": "A mountain at sunset",
"caption": "The Rockies at dusk.",
"credit": "Jane Doe / Getty Images",
"width": 1920,
"height": 1080,
"link": "https://example.com/gallery",
"errors": [],
"warnings": [],
"element": {
"tag": "img",
"attributes": { "src": "https://example.com/photo.jpg" }
}
}component: "gallery"
| Field | Type | Description |
|---|---|---|
images |
GalleryImage[] |
Ordered list of images (see below). |
role |
"default" | "mosaic" (optional)
|
Display variant. |
animation |
"fade" | "slide" | "cube" | "coverflow" | "flip" (optional)
|
Transition style. |
caption |
string | Record<string, string> (optional)
|
Gallery-level caption. |
direction |
"horizontal" | "vertical" (optional)
|
Scroll axis. |
GalleryImage
| Field | Type | Description |
|---|---|---|
imageurl |
string |
Source URL. |
caption |
string (optional)
|
Per-image caption. |
link |
string (optional)
|
URL the image links to. |
alt |
string (optional)
|
Alt text. |
credit |
string (optional)
|
Credit text. |
width |
number (optional)
|
Width in pixels. |
height |
number (optional)
|
Height in pixels. |
{
"component": "gallery",
"role": "default",
"animation": "slide",
"direction": "horizontal",
"images": [
{
"imageurl": "https://example.com/img1.jpg",
"alt": "First slide",
"caption": "Opening shot",
"credit": "AP Photo",
"width": 1200,
"height": 800
},
{
"imageurl": "https://example.com/img2.jpg",
"alt": "Second slide",
"link": "https://example.com/article"
}
],
"errors": [],
"warnings": []
}component: "video"
The base shape covers hosted/direct video files. Platform-specific variants add a vidtype discriminator and a params object.
| Field | Type | Description |
|---|---|---|
url |
string (optional)
|
Source URL (hosted video). |
controls |
boolean (optional)
|
Show player controls. |
autoplay |
boolean (optional)
|
Autoplay on load. |
loop |
boolean (optional)
|
Loop playback. |
muted |
boolean (optional)
|
Start muted. |
movietype |
"hosted" (optional)
|
Signals a directly-hosted file. |
poster |
string (optional)
|
Poster image URL. |
caption |
string (optional)
|
Caption. |
credit |
string (optional)
|
Credit. |
Platform variants (extend the base, add vidtype + params):
| Interface | vidtype |
params |
|---|---|---|
YoutubeComponent |
"youtube" |
{ id: string } |
VimeoComponent |
"vimeo" |
{ id: string } |
DailymotionComponent |
"dailymotion" |
{ id: string } |
TikTokComponent |
"tiktok" |
{ id: string; username: string } |
Hosted video:
{
"component": "video",
"movietype": "hosted",
"url": "https://example.com/clip.mp4",
"poster": "https://example.com/clip-poster.jpg",
"controls": true,
"autoplay": false,
"loop": false,
"muted": false,
"caption": "Highlights from the match.",
"errors": [],
"warnings": []
}YouTube:
{
"component": "video",
"vidtype": "youtube",
"params": { "id": "dQw4w9WgXcQ" },
"controls": true,
"autoplay": false,
"loop": false,
"muted": false,
"errors": [],
"warnings": []
}TikTok:
{
"component": "video",
"vidtype": "tiktok",
"params": { "id": "7123456789012345678", "username": "someuser" },
"errors": [],
"warnings": []
}component: "audio"
| Field | Type | Description |
|---|---|---|
url |
string |
Audio file URL. |
controls |
boolean |
Show player controls. |
autoplay |
boolean |
Autoplay on load. |
loop |
boolean |
Loop playback. |
muted |
boolean |
Start muted. |
caption |
string (optional)
|
Caption. |
credit |
string (optional)
|
Credit. |
{
"component": "audio",
"url": "https://example.com/episode-42.mp3",
"controls": true,
"autoplay": false,
"loop": false,
"muted": false,
"caption": "Episode 42 — Full interview",
"errors": [],
"warnings": []
}component: "twitter"
| Field | Type | Description |
|---|---|---|
height |
string |
Embed height. |
params |
{ id?: string; account?: string } |
Tweet ID and/or account. |
{
"component": "twitter",
"height": "350",
"params": { "id": "1234567890123456789" },
"errors": [],
"warnings": []
}component: "instagram"
| Field | Type | Description |
|---|---|---|
id |
string |
Instagram media ID. |
type |
"post" | "reel" | "tv" |
Content type. |
{
"component": "instagram",
"id": "CxYzAbCdEfG",
"type": "post",
"errors": [],
"warnings": []
}component: "infogram"
| Field | Type | Description |
|---|---|---|
params |
{ id: string; parentUrl: string; src: "embed" } |
Infogram embed parameters. |
{
"component": "infogram",
"params": {
"id": "my-infographic-slug",
"parentUrl": "https://example.com/article",
"src": "embed"
},
"errors": [],
"warnings": []
}component: "htmltable"
| Field | Type | Description |
|---|---|---|
html |
string |
Sanitized HTML string of the full <table> element. |
caption |
string (optional)
|
Table caption. |
credit |
string (optional)
|
Credit. |
{
"component": "htmltable",
"html": "<table><thead><tr><th>Name</th><th>Score</th></tr></thead><tbody><tr><td>Alice</td><td>98</td></tr></tbody></table>",
"caption": "Q1 results",
"errors": [],
"warnings": []
}component: "button"
| Field | Type | Description |
|---|---|---|
text |
string (optional)
|
Button label. |
link |
string (optional)
|
Destination URL. |
{
"component": "button",
"text": "Subscribe now",
"link": "https://example.com/subscribe",
"errors": [],
"warnings": []
}component: "spacer"
| Field | Type | Description |
|---|---|---|
margin |
"margin-1" | "margin-20" | "margin-50" | "margin-75" | "margin-100" |
Vertical spacing size. |
{
"component": "spacer",
"margin": "margin-50",
"errors": [],
"warnings": []
}toSpacer (the default <br> converter, also used by a spacer custom mapping) always sets margin: "margin-20" — there is currently no way to select a different margin from the HTML itself.
component: "divider"
No fields beyond the base shape — a divider carries no configuration of its own, just id/properties/html/element/errors/warnings.
{
"component": "divider",
"errors": [],
"warnings": [],
"element": { "tag": "hr" }
}component: "custom"
Used for elements that do not match any built-in mapping rule.
| Field | Type | Description |
|---|---|---|
content |
string |
Raw re-serialized HTML of the matched element. Not sanitized — use the base html field for a sanitized version. |
node |
unknown |
The raw ElementNode AST node from the HTML parser (component/html/parser.ts). Useful for consumers that need to traverse the original tree. |
link |
ComponentLink (optional)
|
Present when the element was wrapped in an <a> ancestor during mapping. The content field is not modified — read the link from this field instead. |
{
"component": "custom",
"content": "<div class=\"pullquote\"><span>The future is already here.</span></div>",
"html": "<div class=\"pullquote\"><span>The future is already here.</span></div>",
"errors": [],
"warnings": [],
"element": { "tag": "div", "attributes": { "class": "pullquote" } }
}With a resolved link:
{
"component": "custom",
"content": "<div class=\"promo\">Exclusive offer</div>",
"html": "<div class=\"promo\">Exclusive offer</div>",
"link": {
"href": "https://example.com/offer",
"element": {
"tag": "a",
"attributes": { "href": "https://example.com/offer" }
}
},
"errors": [],
"warnings": [],
"element": { "tag": "div", "attributes": { "class": "promo" } }
}component: "container"
A generic container that holds nested components.
| Field | Type | Description |
|---|---|---|
type |
"link" | "figure" (optional)
|
Sub-type discriminator. Absent on plain containers. |
components |
Component[] |
Nested child components. |
{
"component": "container",
"components": [
{
"component": "headline",
"text": "Breaking News",
"errors": [],
"warnings": []
},
{
"component": "body",
"text": "Details are still emerging.",
"errors": [],
"warnings": []
}
],
"errors": [],
"warnings": []
}component: "columns"
| Field | Type | Description |
|---|---|---|
columns |
Component[][] |
Each inner array is one column's components. |
{
"component": "columns",
"columns": [
[
{
"component": "body",
"text": "Left column text.",
"errors": [],
"warnings": []
}
],
[
{
"component": "image",
"imageurl": "https://example.com/side.jpg",
"errors": [],
"warnings": []
}
]
],
"errors": [],
"warnings": []
}component: "live_container"
| Field | Type | Description |
|---|---|---|
posts |
LivePostComponent[] |
Ordered live posts. |
component: "live_post"
| Field | Type | Description |
|---|---|---|
components |
Component[] |
Components within this post. |
{
"component": "live_container",
"posts": [
{
"component": "live_post",
"components": [
{
"component": "headline",
"text": "Update 2 — 14:32",
"errors": [],
"warnings": []
},
{
"component": "body",
"text": "Officials confirm the situation is under control.",
"errors": [],
"warnings": []
}
],
"errors": [],
"warnings": []
},
{
"component": "live_post",
"components": [
{
"component": "headline",
"text": "Update 1 — 13:15",
"errors": [],
"warnings": []
},
{
"component": "body",
"text": "Incident reported downtown.",
"errors": [],
"warnings": []
}
],
"errors": [],
"warnings": []
}
],
"errors": [],
"warnings": []
}component: "recipe"
| Field | Type | Description |
|---|---|---|
recipe |
Recipe (optional)
|
Structured recipe data (ingredients, steps, etc.). Defined by RecipeSchema in schema/recipe-schema.ts. |
url |
string (optional)
|
Canonical URL of the recipe. |
components |
Component[] |
Fallback component representation of the recipe content. |
{
"component": "recipe",
"url": "https://example.com/recipes/banana-bread",
"recipe": {
"name": "Banana Bread",
"prepTime": "PT15M",
"cookTime": "PT1H",
"ingredients": ["3 ripe bananas", "1½ cups flour", "½ cup sugar"],
"steps": [
"Preheat oven to 175°C.",
"Mash bananas.",
"Mix all ingredients and bake for 60 minutes."
]
},
"components": [
{
"component": "headline",
"text": "Banana Bread",
"errors": [],
"warnings": []
},
{
"component": "body",
"text": "3 ripe bananas, 1½ cups flour, ½ cup sugar.",
"errors": [],
"warnings": []
}
],
"errors": [],
"warnings": []
}LinkContainerComponent and FigureContainerComponent are transient types used internally during the HTML mapping pipeline. They do not appear in the final Component[] output delivered to consumers — by the time mapping completes, the link or figure context has been resolved and distributed into the surrounding components.
They are exported and documented here because their type guards (isLinkContainerComponent, isFigureContainerComponent) are available if you need to inspect intermediate mapping state or write custom mapping extensions.
A ContainerComponent with type: "link". Represents an <a> element wrapping mixed content (text, images, etc.) during mapping. The link and attributes are resolved onto child components via the link property (ComponentLink) and this wrapper is discarded from the final output.
| Field | Type | Description |
|---|---|---|
type |
"link" |
Discriminator. |
components |
Component[] |
Child components inside the anchor. |
link |
string (optional)
|
The anchor's href. |
attributes |
Map<string, string> (optional)
|
All attributes of the source <a> element. |
{
"component": "container",
"type": "link",
"link": "https://example.com/story",
"components": [
{
"component": "image",
"imageurl": "https://example.com/thumb.jpg",
"errors": [],
"warnings": []
},
{
"component": "body",
"text": "Read the full story.",
"errors": [],
"warnings": []
}
],
"errors": [],
"warnings": []
}A ContainerComponent with type: "figure". Produced by every <figure> element during mapping. Caption and credit are extracted from <figcaption> children (credit matched by <small>, role="credit", or class="credit"), then this wrapper is resolved into its final container form.
| Field | Type | Description |
|---|---|---|
type |
"figure" |
Discriminator. |
components |
Component[] |
Media and other children inside the figure. |
caption |
string (optional)
|
Extracted caption text. |
credit |
string (optional)
|
Extracted credit text. |
{
"component": "container",
"type": "figure",
"caption": "Crowds gather outside the courthouse.",
"credit": "Reuters",
"components": [
{
"component": "image",
"imageurl": "https://example.com/courthouse.jpg",
"width": 1600,
"height": 900,
"errors": [],
"warnings": []
}
],
"errors": [],
"warnings": []
}Narrow a Component with the exported is* guards rather than checking .component by hand:
import {
isImageComponent,
isVideoComponent,
isTextComponent,
} from '@canvasflow/feed';
for (const c of components) {
if (isImageComponent(c)) {
console.log(c.imageurl, c.caption);
} else if (isVideoComponent(c)) {
console.log(c.vidtype);
} else if (isTextComponent(c)) {
console.log(c.text, c.link?.href);
}
}Available guards:
isAudioComponent isButtonComponent isColumnsComponent
isContainerComponent isCustomComponent isDailymotionComponent
isDividerComponent isFigureContainerComponent isGalleryComponent
isGalleryImage isHTMLTableComponent isImageComponent
isInfogramComponent isInstagramComponent isLinkContainerComponent
isRecipeComponent isSpacerComponent isTextComponent
isTikTokComponent isTwitterComponent isValidTextRole
isVideoComponent isVimeoComponent isYoutubeComponent
Internally, component.ts defines a Zod schema alongside every type — ComponentSchema, ComponentTypeSchema, TextTypeSchema, ComponentLinkSchema, and per-kind schemas such as ImageComponentSchema, TextComponentSchema, CustomComponentSchema — and recipe extraction has its own in schema/recipe-schema.ts. None of these schema objects are re-exported from @canvasflow/feed — only the TypeScript types and the is* guards above are public. If you need to validate a Component from outside this library, narrow it with the is* guards rather than importing a schema.
Start here
Reference
Operations