RenderTextContent
Renders a single content item based on its type. Supports html, markdown, math, and code types.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
item | ContentItem | — | Content item with type and value |
Supported Types
html - Raw HTMLmarkdown - Markdown textmath - Math expressions (centered, KaTeX)code - Code blocks with syntax highlighting
Import
import { RenderTextContent } from '@the-vcsi/scrolly-kit';Usage
<script>
import { RenderTextContent } from '@the-vcsi/scrolly-kit';
</script>
<RenderTextContent item={{ type: 'markdown', value: '## Hello' }} />
<RenderTextContent item={{
type: 'code',
value: 'const x = 1;',
language: 'javascript'
}} />Full Source
💡 Components rely on --vcsi-* tokens from tokens.css. You'd need to either need to @import '@the-vcsi/scrolly-kit/styles/tokens.css'; to access the CSS variables or define equivalent variables in your app.css. We also are using types here to provide hints when users are using the components in their project.
<!--
@component
Renders a single content item based on its type.
Takes a `ContentItem` object and renders it as HTML, Markdown, math, or code
with syntax highlighting. Used internally by ScrollyContent but can be used
standalone for rendering structured content.
## Props
- `item` - A `ContentItem` object with `type` and `value` fields
## Supported Types
- `html` - Raw HTML (rendered with {@html})
- `markdown` - Markdown text (rendered via MarkdownRenderer)
- `math` - Math expressions in markdown (centered, uses KaTeX)
- `code` - Code blocks with syntax highlighting and optional line numbers
## Usage
```svelte
<RenderTextContent item={{ type: 'markdown', value: '## Hello' }} />
<RenderTextContent item={{
type: 'code',
value: 'const x = 1;',
language: 'javascript',
highlightLines: '1'
}} />
```
-->
<script lang="ts">
import Md from './MarkdownRenderer.svelte';
import type { ContentItem } from './ScrollySnippets.svelte';
let { item }: { item: ContentItem } = $props();
const VALID_TYPES = ['html', 'markdown', 'math', 'code'] as const;
/** Escape HTML entities so code displays as text, not rendered HTML */
function escapeHtml(str: string): string {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
}
</script>
{#if item.type === "html"}
<span>{@html item.value}</span>
{:else if item.type === "markdown"}
<Md text={item.value as string}/>
{:else if item.type === "math"}
<div class="math-container">
<Md text={item.value as string}/>
</div>
{:else if item.type === "code"}
{@const rawCode = Array.isArray(item.value) ? item.value.join('\n') : item.value}
{@const codeValue = escapeHtml(rawCode)}
{@const langClass = item.language ? `language-${item.language}` : ''}
{@const highlightAttr = item.highlightLines ? `data-highlight-lines="${item.highlightLines}"` : ''}
<div class="code-block">
{#if item.language}
<div class="code-language">{item.language}</div>
{/if}
<Md text={`<pre><code class="${langClass} show-line-numbers" ${highlightAttr}>${codeValue}</code></pre>`}/>
</div>
{:else}
{@const _ = console.warn(`[RenderTextContent] Unknown type: "${(item as any).type}". Valid: ${VALID_TYPES.join(', ')}`)}
{/if}
<style>
.math-container {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
}
.code-block {
position: relative;
max-width: 100%;
overflow-x: auto;
}
/* Reset pre margin inside code-block so language badge aligns with the box */
.code-block :global(pre) {
margin: 0;
}
.code-language {
position: absolute;
top: 0;
right: 0;
background: #e1e4e8;
color: #57606a;
border-color: #d1d9e0;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0 6px 0 6px;
border-left: 1px solid;
border-bottom: 1px solid;
z-index: 1;
}
</style>