Skip to content

Component Types

github-actions[bot] edited this page Sep 11, 2026 · 8 revisions

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


The base shape

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

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 and TextType

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, or text1text60.
  • 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 text1text60 range is generated at the type level, backed by a runtime Set for validation.


Component type reference

Text components (TextComponent)

component: any TextType value — headline, title, subtitle, intro, body, crosshead, byline, blockquote, footer, imagecaption, or text1text60.

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:

  • h1headline
  • h2title
  • h3subtitle
  • h4intro
  • h5crosshead
  • h6byline
  • p, ol, ul, abody
  • blockquoteblockquote
  • footerfooter

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" }
}

ImageComponent

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 a FigureContainerComponent (see Transient components) that holds the ImageComponent as a child. A bare <img> outside a <figure> produces a standalone ImageComponent. A <picture> with more than one <img> child records a DUPLICATE_IMG_TAG warning 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" }
  }
}

GalleryComponent

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": []
}

VideoComponent

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": []
}

AudioComponent

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": []
}

TwitterComponent

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": []
}

InstagramComponent

component: "instagram"

Field Type Description
id string Instagram media ID.
type "post" | "reel" | "tv" Content type.
{
  "component": "instagram",
  "id": "CxYzAbCdEfG",
  "type": "post",
  "errors": [],
  "warnings": []
}

InfogramComponent

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": []
}

HTMLTableComponent

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": []
}

ButtonComponent

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": []
}

SpacerComponent

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.


DividerComponent

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" }
}

CustomComponent

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" } }
}

ContainerComponent

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": []
}

ColumnsComponent

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": []
}

LiveContainerComponent

component: "live_container"

Field Type Description
posts LivePostComponent[] Ordered live posts.

LivePostComponent

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": []
}

RecipeComponent

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": []
}

Transient components

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.

LinkContainerComponent

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": []
}

FigureContainerComponent

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": []
}

Type guards

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

Runtime schemas

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.

Clone this wiki locally