Skip to content

feat: add defaultComponent fallback prop#548

Open
sleitor wants to merge 1 commit into
vercel:mainfrom
sleitor:feat/default-component-fallback
Open

feat: add defaultComponent fallback prop#548
sleitor wants to merge 1 commit into
vercel:mainfrom
sleitor:feat/default-component-fallback

Conversation

@sleitor

@sleitor sleitor commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new defaultComponent prop to <Streamdown> — an additive, backwards-compatible fallback renderer for any HTML tag or allowed custom tag that does not have an explicit entry in the components map.

What it does

When defaultComponent is set, it is used for:

  • Custom tags declared via allowedTags that have no matching key in components
  • Standard HTML tags not covered by the built-in Tailwind component set (e.g. <span>, <div>, <section>)

Explicit components entries always take precedence over defaultComponent.

Why

This directly addresses the boilerplate-reduction use case from Refs #543: users who want a pass-through/unstyled renderer no longer need to enumerate every tag individually.

// Before: 25+ explicit component entries just to get plain HTML
<Streamdown components={{ h1: ..., h2: ..., p: ..., ... }}>{md}</Streamdown>

// After: one prop, covers everything not explicitly overridden
<Streamdown
  defaultComponent={({ node, children, ...props }) =>
    createElement(node!.tagName, props, children)
  }
>
  {md}
</Streamdown>

Scope note

This PR implements only the defaultComponent fallback — the clean, additive, backwards-compatible piece of Refs #543. The broader Streamdown / StreamdownUnstyled split and className-merge semantic change described in the issue are architecture-level decisions that involve breaking changes and were intentionally left out of this PR.

Implementation details

  • The Proxy approach is used on the components map so that hast-util-to-jsx-runtime's hasOwnProperty checks (via own.call) see the fallback for uncovered tag names — both getOwnPropertyDescriptor and get traps are intercepted.
  • allowedTags entries are also eagerly registered in the map for clarity and correct Block-comparator invalidation.
  • When defaultComponent is undefined, behaviour is 100% unchanged (no Proxy created).

Checklist

  • Backwards compatible — no behaviour change when prop is absent
  • Tests added (__tests__/default-component.test.tsx, 9 tests)
  • All 991 existing tests pass
  • Biome lint + format clean (pnpm check)
  • TypeScript clean for changed files
  • Changeset added (minor bump)
  • README updated with usage example

Refs #543

…ering

Adds a new `defaultComponent` prop to `<Streamdown>`. When set, it acts as
a fallback renderer for any HTML tag or allowed custom tag that does not have
an explicit entry in the `components` map.

- Applies to custom tags in `allowedTags` without an explicit component
- Applies to HTML tags absent from the built-in Tailwind component set
- Explicit `components` entries always take precedence
- Backward-compatible: omitting the prop preserves existing behaviour
- Uses a Proxy on the components map so hast-util-to-jsx-runtime's
  hasOwnProperty checks see the fallback for uncovered tag names

Refs vercel#543
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@sleitor is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant