Markdown
Renderiza Markdown (inclusive a saída de uma IA token a token) como um web component independente de framework. O <r-markdown> é modelado no Streamdown da Vercel: enquanto o texto chega, ele fecha na hora o **bold, o `code, os links e a matemática $$ digitados pela metade, divide o documento em blocos e redesenha só o bloco que mudou, de modo que uma resposta longa nunca é reanalisada do início a cada token.
Blocos cercados com ```mermaid viram <r-mermaid>, a matemática vira <r-math>, e o código pode ser destacado com shiki; cada um deles é carregado sob demanda na primeira vez que o conteúdo precisa. A saída é sanitizada com DOMPurify.
Use quando exibir Markdown que você não controla por inteiro (respostas de chat, fluxos de LLM, comentários de usuários, documentação) e quiser streaming, suporte a código, diagramas e matemática, e HTML seguro, sem ter de ligar você mesmo um parser, um sanitizador e um destacador.
Início rápido
<r-markdown copy highlight content="# Olá ..."></r-markdown>import 'ranui'; // ou a entrada independente:
import 'ranui/markdown';A fonte é lida da propriedade content (a preferida: não é refletida, então transmitir uma resposta longa não agita o DOM), do atributo content ou do texto do próprio elemento:
const el = document.createElement('r-markdown');
el.setAttribute('caret', ''); // mostra um cursor piscando enquanto o texto chega
for await (const chunk of stream) {
el.content += chunk; // só o último bloco é redesenhado
}
el.removeAttribute('caret');
container.append(el);Streaming
mode="streaming" (o padrão) passa o texto primeiro pelo remend, o finalizador de markdown incompleto extraído do Streamdown: assim um **bold recebido pela metade sai em negrito em vez de mostrar asteriscos, [text](https://exa fica como texto simples até a URL fechar, um - não transforma o parágrafo anterior em título, e por aí vai. Para documentos já prontos, use mode="static" e pule essa passagem, renderizando tudo de uma vez.
<r-markdown caret content="*Ênfase* pela metade, `código em linha` e **negrito que ainda está chegando"></r-markdown>- cursor:
caretmostra um▋piscando ecaret="circle"um●, depois do último bloco. Ele se esconde sozinho enquanto uma cerca de código continua aberta ou o último bloco é uma tabela. - cercas de código incompletas ficam em texto simples (sem lampejo de destaque, sem diagrama pela metade) até a cerca de fechamento chegar; nesse meio-tempo o contêiner carrega
data-incomplete.
Blocos de código
Todo bloco de código ganha um cabeçalho com o nome da linguagem e, se você ativar, botões de copiar e baixar. Acrescente highlight para destacar a sintaxe com shiki (carregado sob demanda; as linguagens chegam conforme o uso; github-light / github-dark por padrão, seguindo o tema da página).
<r-markdown copy download line-numbers highlight></r-markdown>
<!-- escolha os temas: claro escuro -->
<r-markdown highlight="vitesse-light vitesse-dark"></r-markdown>Mermaid e matemática
```mermaid→<r-mermaid>(com tela cheia;copy/downloadsão repassados).$$…$$,\[…\]e```math→<r-math>em bloco;\(…\)→ em linha. O cifrão simples$…$precisa ser ativado cominline-math, porque se confunde com valores em dinheiro.
Referência da API
Atributos
| Atributo | Tipo | Padrão | Descrição |
|---|---|---|---|
content | string | — | Fonte Markdown. A propriedade content tem precedência e não é refletida; na falta das duas, usa o texto do elemento. |
mode | 'streaming' | 'static' | 'streaming' | streaming fecha o markdown incompleto e compara por bloco; static desenha o texto inteiro como está, de uma vez. |
caret | booleano / 'circle' | desligado | Cursor piscando depois do último bloco (▋, ou ● com circle). |
copy | booleano | desligado | Botão de copiar nos blocos de código (repassado ao <r-mermaid> embutido). |
download | booleano | desligado | Botão de baixar nos blocos de código (code.<ext> conforme a linguagem). |
line-numbers | booleano | desligado | Números de linha nos blocos de código. |
highlight | booleano / nomes de tema "claro escuro" | desligado | Destaque de sintaxe via shiki. Sozinho → github-light github-dark; um nome → os dois; dois nomes → claro / escuro. |
inline-math | booleano | desligado | Trata $…$ como matemática em linha (\(…\) sempre é). |
link-target | string | '_blank' | target dos links externos (com rel="noopener noreferrer" acrescentado). _self deixa os links intactos. As #âncoras da própria página nunca recebem. |
theme | 'auto' | 'light' | 'dark' | 'auto' | Tema do destaque e dos diagramas. auto segue a página (.dark, [data-ran-theme], senão prefers-color-scheme). |
sheet | string | — | CSS extra injetado no shadow root. |
label-* | string | inglês | Sobrescreve os rótulos dos controles: label-copy, label-download. |
Apelidos de propriedade: content, mode, caret, copyable, downloadable, lineNumbers, highlight, inlineMath, linkTarget, theme, sheet.
Eventos
Todos os eventos borbulham e cruzam a fronteira do shadow DOM (composed).
| Evento | detail | Dispara quando |
|---|---|---|
render | { blocks: number, changed: number } | uma passagem de desenho mudou pelo menos um bloco |
copied | { kind: 'code', language, code } | um bloco de código foi copiado |
download | { kind: 'code', language, filename } | um bloco de código foi baixado |
error | { message: string } | a análise ou o desenho falhou (também aparece no lugar) |
Parts CSS
| Part | Descrição |
|---|---|
markdown | O invólucro externo. |
body | O contêiner dos blocos. |
block | Cada bloco desenhado. |
code | O contêiner de um bloco de código. |
code-header | A barra de linguagem e ações de um bloco. |
code-lang | O rótulo da linguagem. |
code-actions | O grupo de botões de ação. |
button | Cada botão de copiar ou baixar. |
table | O invólucro de tabela com rolagem horizontal. |
error | A caixa de erro (quando o desenho falha). |
r-markdown::part(code) {
border-radius: 8px;
}Variáveis CSS
Sobrescreva no elemento (cada uma recai num token semântico e depois num valor literal): --ran-markdown-color, --ran-markdown-font-size, --ran-markdown-line-height, --ran-markdown-gap, --ran-markdown-heading-color, --ran-markdown-link-color, --ran-markdown-inline-code-bg, --ran-markdown-code-bg, --ran-markdown-code-border, --ran-markdown-code-radius, --ran-markdown-code-font-size, --ran-markdown-mono-font, --ran-markdown-blockquote-border, --ran-markdown-table-border, --ran-markdown-table-header-bg, --ran-markdown-caret, --ran-markdown-caret-color, --ran-markdown-button-color, --ran-markdown-error-color.
Notas
- Carregado sob demanda: o pedaço do parser (marked + DOMPurify + remend) carrega no primeiro desenho; shiki, mermaid e Temml carregam só quando o conteúdo os usa. Aplicações que nunca desenham markdown não pagam nada.
- Sanitizado: o HTML cru dentro do markdown passa pelo DOMPurify: scripts, manipuladores de eventos, URLs
javascript:,<style>, formulários e iframes são removidos. As caixas de seleção de listas de tarefas sobrevivem. - A comparação por blocos os indexa por posição, então o estado do DOM dentro de blocos intocados (um diagrama aberto em tela cheia, uma tabela rolada) sobrevive às atualizações do streaming. O documento é lexado uma vez e cada bloco é desenhado a partir dos próprios tokens, de modo que uma definição de referência de link se resolve entre blocos (
[text][id]num bloco,[id]: urlem outro). - As notas de rodapé do GFM (
[^1]) não têm suporte: o marked não tem tokenizador de notas de rodapé, então os marcadores saem como texto literal. - O shiki é resolvido a partir da sua própria instalação. A compilação ES deixa o
import('shiki')intocado, então o seu empacotador o separa em pedaços e baixa só as gramáticas que as suas cercas de código usam. O shiki é uma dependência normal do ranui, entãonpm i ranuijá o traz; não há nada a acrescentar. - IIFE independente: o
dist/iife/markdown.iife.jsnão tem resolvedor, então embute mermaid, Temml e o pacote de linguagens web do shiki (uns 50 idiomas comuns). Para cobertura completa de linguagens e um download menor, prefira a entrada ES (ranui/markdown).