@sergiogdev/markdown-viewer
v0.1.0
Published
Visor de Markdown para React/Next, tematizable con custom properties CSS. Soporta GFM, tablas, columnas :::columns y resaltado de sintaxis opcional.
Maintainers
Readme
@sergiogdev/markdown-viewer
Visor de Markdown para React/Next, pensado para renderizar el contenido de los posts de MiTablón en cualquiera de las landings, con un aspecto distinto en cada web sin tocar el paquete.
- Server Component por defecto: sin
"use client", no añade nada al bundle del navegador cuando se usa en el servidor. - Tematizable con custom properties CSS (
--mv-*). No depende de Tailwind, así que funciona igual en un proyecto con Tailwind v4, con v3 o sin Tailwind. - GFM (tablas, tachado), columnas
:::columnsdel dialecto de MiTablón y resaltado de sintaxis opcional. - HTML crudo saneado con
rehype-sanitize: el Markdown lo escribe el cliente desde el panel, no es contenido de confianza.
Instalación
npm install @sergiogdev/markdown-viewer
# opcional, solo si quieres resaltado de sintaxis:
npm install react-syntax-highlighterUso
import { MarkdownViewer } from "@sergiogdev/markdown-viewer";
import "@sergiogdev/markdown-viewer/styles.css"; // una vez, en el layout raíz
export default function Post({ content }: { content: string }) {
return <MarkdownViewer content={content} as="article" className="blog-article" />;
}El import del CSS va una sola vez por aplicación, normalmente en
app/layout.tsx o en tu globals.css (@import "@sergiogdev/markdown-viewer/styles.css";).
Personalización
Tres niveles, de menos a más invasivo. Usa siempre el más bajo que te sirva.
1. Tokens CSS — el 90% de los casos
Redefine las variables donde quieras: en :root para toda la web, o en una
clase que apliques al visor para tener varios temas en la misma página.
.blog-article {
--mv-font-body: var(--font-open-sans);
--mv-font-heading: var(--font-montserrat);
--mv-font-quote: var(--font-playfair);
--mv-accent: #d97706; /* enlaces */
--mv-quote-border-color: #fbbf24; /* borde de la cita */
--mv-accent-soft: #fffbeb; /* fondo del thead */
--mv-accent-border: #fde68a; /* borde inferior del th */
}Los valores por defecto se declaran en :where(:root), de especificidad 0, así
que cualquier declaración tuya gana, esté en :root o en un ancestro.
Tokens disponibles
| Grupo | Tokens |
|---|---|
| Fuentes | --mv-font-body --mv-font-heading --mv-font-quote --mv-font-mono |
| Tamaños | --mv-text --mv-text-h1…--mv-text-h4 --mv-text-quote --mv-text-code --mv-text-pre --mv-text-table |
| Color | --mv-fg --mv-fg-strong --mv-fg-muted --mv-accent --mv-accent-strong --mv-accent-soft --mv-accent-border --mv-border --mv-code-bg --mv-code-fg --mv-pre-bg --mv-pre-fg |
| Ritmo | --mv-leading --mv-leading-heading --mv-block-gap --mv-space-h1…--mv-space-h4 --mv-space-block --mv-list-indent |
| Forma | --mv-radius --mv-radius-sm --mv-quote-border --mv-quote-border-color --mv-weight-heading --mv-weight-strong --mv-tracking-heading |
2. classNames — retoques puntuales
Añade clases a un elemento concreto sin reemplazarlo. Se suman a las clases
base (mv-h2, mv-p…); como las reglas del paquete van envueltas en
:where(), una sola utilidad tuya basta para pisarlas.
<MarkdownViewer
content={content}
classNames={{
img: "shadow-sm",
blockquote: "md:-mx-6",
}}
/>Slots: h1–h6, p, a, strong, em, del, ul, ol, li,
blockquote, hr, img, code, pre, tableWrapper, table, thead,
tbody, tr, th, td.
3. components — sustitución total
Reemplaza el renderizador de un elemento. Lo que pases aquí sustituye al
componente por defecto: ni las clases base ni classNames se aplican ya a ese
slot.
import Image from "next/image";
<MarkdownViewer
content={content}
components={{
img: ({ src, alt }) =>
typeof src === "string" ? (
<Image src={src} alt={alt ?? ""} width={1200} height={675} className="mv-img" />
) : null,
}}
/>Si solo quieres envolver el comportamiento por defecto, impórtalo y compón:
import { createMarkdownComponents } from "@sergiogdev/markdown-viewer";
const base = createMarkdownComponents();Resaltado de sintaxis
Vive en un subpath aparte para que quien no lo use no pague el peso de
react-syntax-highlighter ni de su tema. El tema no se incluye en el paquete:
elígelo tú.
import { createCodeHighlightComponents } from "@sergiogdev/markdown-viewer/code";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
<MarkdownViewer
content={content}
components={createCodeHighlightComponents({ style: oneDark })}
/>Se engancha en pre, no en code: el código en línea conserva su estilo y un
bloque cercado sin lenguaje declarado sigue cayendo en el <pre> normal.
Columnas (:::columns)
El dialecto que produce el editor de MiTablón:
:::columns
::left
Contenido de la izquierda.
::right
Contenido de la derecha.
:::Se renderiza como un grid de dos columnas a partir de 768px, una sola columna
por debajo. Desactivable con enableColumns={false}; el pre-procesado solo se
ejecuta si el contenido contiene realmente :::columns.
Props
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| content | string | — | Markdown a renderizar |
| className | string | — | Clases del contenedor raíz (se añaden a mv-root) |
| classNames | MarkdownClassNames | {} | Clases extra por slot |
| components | Components | {} | Sustitución total de componentes |
| enableGFM | boolean | true | Tablas, tachado, listas de tareas |
| enableColumns | boolean | true | Bloques :::columns |
| as | "div" \| "article" \| "section" \| "main" | "div" | Etiqueta del contenedor |
| id | string | — | id del contenedor |
Seguridad
rehype-raw solo se activa cuando hay columnas que renderizar, y siempre va
seguido de rehype-sanitize. El esquema por defecto se amplía únicamente con
div + className/data-type para los contenedores de columnas: scripts,
handlers on* y style se siguen eliminando.
