---
title: Component overrides
description: Replace semantic Markdown elements with application components in React or Vue without changing Smoothstream's reveal plan.
---

React and Vue can replace elements that originate in Markdown with framework-native components. Parsing, sanitization, structural stabilization, scheduling, table shells, image readiness, and fenced-code behavior remain owned by Smoothstream.

<Tabs>
  <Tab label="React" icon="/icons/react.svg">
    ```tsx
    import {
      Smoothstream,
      type SmoothstreamComponents,
    } from "@smoothstream/react";

    const components: SmoothstreamComponents = {
      h2: ({ children, className, ...props }) => (
        <h2 {...props} className={`app-heading ${className ?? ""}`}>
          {children}
        </h2>
      ),
      a: ({ children, className, ...props }) => (
        <a
          {...props}
          className={`app-link ${className ?? ""}`}
        >
          {children}
        </a>
      ),
    };

    <Smoothstream components={components} receiving={receiving}>
      {markdown}
    </Smoothstream>
    ```
  </Tab>
  <Tab label="Vue" icon="/icons/vue.svg">
    ```vue
    <script setup lang="ts">
    import { Smoothstream } from "@smoothstream/vue";
    import AppHeading from "./AppHeading.vue";
    import AppLink from "./AppLink.vue";

    const components = {
      h2: AppHeading,
      a: AppLink,
    };
    </script>

    <template>
      <Smoothstream
        :components="components"
        :markdown="markdown"
        :receiving="receiving"
      />
    </template>
    ```

    A Vue override receives its content through the default slot. Let fallthrough attributes reach its root element, or forward `$attrs` explicitly when `inheritAttrs` is disabled.
  </Tab>
</Tabs>

## Available components

| Category | Names |
| --- | --- |
| Headings | `h1`, `h2`, `h3`, `h4`, `h5`, `h6` |
| Text | `p`, `strong`, `em`, `del` |
| Lists | `ul`, `ol`, `li` |
| Links | `a` |
| Code | `inlineCode` |
| Quotes | `blockquote` |
| Tables | `table`, `thead`, `tbody`, `tr`, `th`, `td` |
| Media | `img` |
| Other | `hr`, `br` |

`inlineCode` replaces Markdown code spans such as `` `npm install` ``. Fenced code is deliberately not part of this API because Smoothstream owns its line admission, highlighting, language label, copy control, and streaming presentation.

Task-list checkboxes, the Markdown root, reveal spans, table scroll wrappers, image state, code controls, syntax tokens, and accessibility announcers are also internal rather than override targets.

## Forward the supplied properties

Overrides receive their rendered children and the sanitized properties of the original element, including `href`, `src`, `alt`, classes, accessibility attributes, and Smoothstream's functional `data-*` attributes and styles. Confirmed links supply their `href` and `title` from the first render, while their labels are still animating. Render the supplied children without converting them to text, and forward properties you do not intentionally replace.

<Callout type="warning" title="Functional properties are part of playback">
  Discarding supplied attributes or styles can remove link safety, image visibility, list-marker animation, table-row state, or other reveal behavior. A footnote reference can intentionally have no `href` while its target has not appeared in the document.
</Callout>

The default theme continues to apply when an override ultimately renders the same semantic element and forwards its properties. If it changes the element structure, it owns the resulting semantics and styling.

## Group children in flex and grid layouts

During a reveal, `children` can temporarily contain multiple elements representing the text being presented. If an override places those children directly in a flex or grid container, each temporary element can become a separate layout item. Properties such as `gap` may then add space between characters or words until the content settles.

Group the streamed children in a semantically appropriate element when the override uses direct-child layout or adds sibling UI:

<Tabs>
  <Tab label="React" icon="/icons/react.svg">
    ```tsx
    const Heading = ({ children, className, ...props }) => (
      <h2
        {...props}
        className={`app-heading ${className ?? ""}`}
      >
        <span className="app-heading-content">
          {children}
        </span>
        <span aria-hidden="true" className="app-heading-badge">
          Custom
        </span>
      </h2>
    );
    ```
  </Tab>
  <Tab label="Vue" icon="/icons/vue.svg">
    ```vue
    <template>
      <h2 v-bind="$attrs" class="app-heading">
        <span class="app-heading-content">
          <slot />
        </span>
        <span aria-hidden="true" class="app-heading-badge">
          Custom
        </span>
      </h2>
    </template>
    ```
  </Tab>
</Tabs>

```css
.app-heading {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.app-heading-content {
  min-width: 0;
}
```

The wrapper becomes one flex or grid item while Smoothstream's reveal elements remain inside it. It is unnecessary for overrides that keep normal inline or block flow. Choose a wrapper that is valid for the element being rendered: a `span` works for heading and other phrasing content, but must not be inserted directly beneath structural elements such as `ul`, `ol`, `table`, `thead`, `tbody`, or `tr`. The extra wrapper also changes direct-child selectors such as `h2 > a`, so add it only where the layout needs it.

## Keep configuration stable

Changing `components` does not reparse or reschedule Markdown, but replacing a component type can remount that rendered subtree. Define the map outside render or memoize it when possible, and treat it as per-response configuration.

Both adapters export `SmoothstreamComponentName` and `SmoothstreamComponents`; React also exports `SmoothstreamComponentProps<Name>`. Smoothstream does not expose its HAST nodes through component props; the adapter-facing syntax tree remains an internal implementation detail.

The vanilla DOM adapter has no component runtime and does not accept `components`. Use semantic CSS selectors and `unstyled` with that adapter.
