@affanhamid/markdown-renderer 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # @affanhamid/markdown-renderer
2
2
 
3
- A React markdown renderer built for AI-generated content. Handles the math rendering problems that react-markdown + remark-math can't.
3
+ > A React markdown renderer built for AI-generated content. One import. Math, code, diagrams, callouts all handled.
4
4
 
5
- ## Why this exists
5
+ ## The Problem
6
6
 
7
7
  If you've used `react-markdown` with `remark-math` and `rehype-katex` to render LLM output, you've hit these problems:
8
8
 
9
- **1. Dollar signs break everything.** `remark-math` uses `$` for LaTeX math, but `$` is also currency. Their `singleDollarTextMath: false` option disables single-dollar math entirely, forcing `$$` for everything. This package disambiguates intelligently: `$20` renders as currency (digit follows `$`), while `$x + y$` renders as math. It also handles CJK characters, Devanagari/Hindi punctuation, and fullwidth punctuation as valid math boundaries.
10
-
11
- **2. AI models use inconsistent math delimiters.** GPT, Claude, and Gemini variously output `$...$`, `$$...$$`, `\(...\)`, and `\[...\]`. `remark-math` does not support `\(...\)` or `\[...\]` there's an [open discussion](https://github.com/remarkjs/remark-math/issues) with no resolution. This package normalizes all four formats automatically before rendering.
12
-
13
- **3. Too many moving parts.** The standard setup requires `react-markdown` + `remark-math` + `remark-gfm` + `rehype-katex` + KaTeX CSS + a syntax highlighter + custom components for tables, images, code blocks. This package is one import.
9
+ | Problem | Details |
10
+ |---------|---------|
11
+ | **Dollar signs break everything** | `remark-math` uses `$` for math, but `$` is also currency. Their `singleDollarTextMath: false` disables single-dollar math entirely. This package disambiguates intelligently: `$20` currency, `$x + y$` math. |
12
+ | **Inconsistent math delimiters** | GPT, Claude, and Gemini variously output `$`, `$$`, `\(…\)`, and `\[…\]`. `remark-math` doesn't support `\(…\)` or `\[…\]`. This package normalizes all four formats automatically. |
13
+ | **Too many moving parts** | The standard setup: `react-markdown` + `remark-math` + `remark-gfm` + `rehype-katex` + KaTeX CSS + syntax highlighter + custom components. This package is **one import**. |
14
14
 
15
15
  ## Features
16
16
 
@@ -21,6 +21,8 @@ If you've used `react-markdown` with `remark-math` and `rehype-katex` to render
21
21
  - **Executable code blocks** — optional `onRunCode` callback for running Python, R, etc.
22
22
  - **Inline images** — `![alt](url)` works inside paragraphs, not just as standalone blocks
23
23
  - **Semantic color tags** — `{color:important}text{/color}` for highlighting (important, definition, example, note, formula)
24
+ - **Callout blocks** — LaTeX-style `\begin{callout}{color}...\end{callout}` with any Tailwind color name
25
+ - **Mermaid diagrams** — fenced `mermaid` code blocks render as diagrams (client-side hydration with dynamic import)
24
26
  - **Auto-scaling brackets** — `($x + y$)` automatically uses `\left(` and `\right)` for proper sizing
25
27
  - **Prompt appendix** — exported `MATH_MARKDOWN_RULES_APPENDIX` string to append to your LLM system prompt, steering models toward consistent delimiter usage
26
28
 
@@ -98,6 +100,36 @@ import { MATH_MARKDOWN_RULES_APPENDIX } from "@affanhamid/markdown-renderer";
98
100
  const systemPrompt = `You are a helpful assistant.\n\n${MATH_MARKDOWN_RULES_APPENDIX}`;
99
101
  ```
100
102
 
103
+ ### Callout blocks
104
+
105
+ Use LaTeX-style syntax with any Tailwind color name:
106
+
107
+ ```md
108
+ \begin{callout}{amber}
109
+ **Warning:** This operation is irreversible.
110
+ \end{callout}
111
+
112
+ \begin{callout}{green}
113
+ **Tip:** Use `Ctrl+S` to save quickly.
114
+ \end{callout}
115
+ ```
116
+
117
+ Renders as styled callout boxes with `border-{color}-200`, `bg-{color}-50`, and `text-{color}-900` classes.
118
+
119
+ ### Mermaid diagrams
120
+
121
+ Fenced code blocks with `mermaid` as the language render as diagrams:
122
+
123
+ ````md
124
+ ```mermaid
125
+ graph LR
126
+ A[Input] --> B[Process]
127
+ B --> C[Output]
128
+ ```
129
+ ````
130
+
131
+ On the client, mermaid is dynamically imported and renders after hydration. For server-side rendering (`renderMarkdownToHtml`), a `<pre>` fallback is output inside a `.md-mermaid` container with `data-mermaid-code` for later hydration.
132
+
101
133
  ## API
102
134
 
103
135
  ### `<MarkdownRenderer />` (default export)
@@ -105,6 +137,7 @@ const systemPrompt = `You are a helpful assistant.\n\n${MATH_MARKDOWN_RULES_APPE
105
137
  | Prop | Type | Default | Description |
106
138
  |------|------|---------|-------------|
107
139
  | `markdown` | `string` | required | Markdown content to render |
140
+ | `className` | `string` | `undefined` | CSS class applied to the wrapper `<div>` |
108
141
  | `onRunCode` | `(code: string, language: string) => Promise<CodeExecutionResult>` | `undefined` | Callback for executing code blocks |
109
142
  | `executableLanguages` | `string[]` | `["python", "r"]` | Languages that get a "Run" button |
110
143
 
@@ -130,6 +163,100 @@ Normalizes `\(...\)` to `$...$` and `\[...\]` to `$$...$$`. Converts inline `$$.
130
163
 
131
164
  A plain-text string with math formatting rules to append to LLM system prompts.
132
165
 
166
+ ## Theming with Tailwind CSS
167
+
168
+ The `className` prop sets a class on the outer wrapper `<div>`, which you can use as a scoping selector for custom themes.
169
+
170
+ ```tsx
171
+ <MarkdownRenderer markdown={content} className="my-theme" />
172
+ ```
173
+
174
+ This renders:
175
+
176
+ ```html
177
+ <div class="my-theme">
178
+ <h1>...</h1>
179
+ <p>...</p>
180
+ <div class="md-callout ...">...</div>
181
+ <!-- etc. -->
182
+ </div>
183
+ ```
184
+
185
+ ### Creating a theme file
186
+
187
+ Create a CSS file (e.g. `markdown-theme.css`) that uses descendant selectors scoped to your theme class. With Tailwind, use `@apply` for utility-based styling:
188
+
189
+ ```css
190
+ /* markdown-theme.css */
191
+
192
+ .my-theme h1 {
193
+ @apply text-3xl font-bold mt-8 mb-4 text-gray-900;
194
+ }
195
+
196
+ .my-theme h2 {
197
+ @apply text-2xl font-semibold mt-6 mb-3 text-gray-800;
198
+ }
199
+
200
+ .my-theme p {
201
+ @apply text-base leading-7 mb-4 text-gray-700;
202
+ }
203
+
204
+ .my-theme code {
205
+ @apply bg-gray-100 text-sm px-1.5 py-0.5 rounded font-mono;
206
+ }
207
+
208
+ .my-theme pre {
209
+ @apply rounded-lg my-4 overflow-x-auto;
210
+ }
211
+
212
+ .my-theme .md-callout {
213
+ @apply border-l-4 border-blue-400 bg-blue-50 px-4 py-3 my-4 rounded-r-lg;
214
+ }
215
+
216
+ .my-theme table {
217
+ @apply w-full border-collapse my-4 text-sm;
218
+ }
219
+
220
+ .my-theme th {
221
+ @apply bg-gray-50 font-semibold text-left px-3 py-2 border border-gray-200;
222
+ }
223
+
224
+ .my-theme td {
225
+ @apply px-3 py-2 border border-gray-200;
226
+ }
227
+ ```
228
+
229
+ Import the theme file in your app and pass the matching class name:
230
+
231
+ ```tsx
232
+ import "./markdown-theme.css";
233
+
234
+ <MarkdownRenderer markdown={content} className="my-theme" />
235
+ ```
236
+
237
+ ### Semantic CSS classes
238
+
239
+ The renderer outputs these classes that you can target in your theme:
240
+
241
+ | Class | Element |
242
+ |-------|---------|
243
+ | `md-callout` | Callout/admonition blocks (`> [!NOTE]`, etc.) |
244
+ | `md-code-block` | Executable code block wrapper |
245
+ | `md-code-block-header` | Header bar above executable code blocks |
246
+ | `md-code-output` | Code execution output area |
247
+ | `md-code-error` | Code execution error output |
248
+ | `md-mermaid` | Mermaid diagram container |
249
+ | `md-run-btn` | "Run" button on executable code blocks |
250
+
251
+ ### Works with server-side rendering
252
+
253
+ `renderMarkdownToHtml()` produces the same HTML structure (wrapped in `<div class="prose max-w-none">`), so the same theme CSS applies to both client and server-rendered output. For server rendering, scope your selectors to the `prose` class or add a wrapper with your theme class:
254
+
255
+ ```ts
256
+ const html = renderMarkdownToHtml(content);
257
+ const themed = `<div class="my-theme">${html}</div>`;
258
+ ```
259
+
133
260
  ## License
134
261
 
135
262
  MIT
package/dist/index.cjs CHANGED
@@ -922,6 +922,7 @@ function renderMarkdownToHtml(markdown, options) {
922
922
  }
923
923
  var MarkdownRenderer = ({
924
924
  markdown,
925
+ className,
925
926
  onRunCode,
926
927
  executableLanguages = ["python", "r"]
927
928
  }) => {
@@ -1103,7 +1104,7 @@ var MarkdownRenderer = ({
1103
1104
  alive = false;
1104
1105
  };
1105
1106
  }, [html]);
1106
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, dangerouslySetInnerHTML: { __html: html } });
1107
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className, dangerouslySetInnerHTML: { __html: html } });
1107
1108
  };
1108
1109
  var markdown_renderer_default = MarkdownRenderer;
1109
1110
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -7,13 +7,14 @@ interface CodeExecutionResult {
7
7
  }
8
8
  interface MarkdownRendererProps {
9
9
  markdown: string;
10
+ className?: string;
10
11
  onRunCode?: (code: string, language: string) => Promise<CodeExecutionResult>;
11
12
  executableLanguages?: string[];
12
13
  }
13
14
  declare function renderMarkdownToHtml(markdown: string, options?: {
14
15
  executableLanguages?: string[];
15
16
  }): string;
16
- declare const MarkdownRenderer: ({ markdown, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
17
+ declare const MarkdownRenderer: ({ markdown, className, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
17
18
 
18
19
  declare function normalizeMathMarkdownDelimiters(markdown: string): string;
19
20
  declare const MATH_MARKDOWN_RULES_APPENDIX = "Math formatting rules (must follow):\n- Inline math: use single-dollar delimiters like $...$.\n- Display math: use $$ delimiters on their own lines with nothing else on those lines.\n- Do not use \\(...\\) or \\[...\\] delimiters.\n- Do not place display-math $$...$$ inside bullets or table cells; use inline $...$ there.\n- Escape non-math currency dollars as \\$.";
package/dist/index.d.ts CHANGED
@@ -7,13 +7,14 @@ interface CodeExecutionResult {
7
7
  }
8
8
  interface MarkdownRendererProps {
9
9
  markdown: string;
10
+ className?: string;
10
11
  onRunCode?: (code: string, language: string) => Promise<CodeExecutionResult>;
11
12
  executableLanguages?: string[];
12
13
  }
13
14
  declare function renderMarkdownToHtml(markdown: string, options?: {
14
15
  executableLanguages?: string[];
15
16
  }): string;
16
- declare const MarkdownRenderer: ({ markdown, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
17
+ declare const MarkdownRenderer: ({ markdown, className, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
17
18
 
18
19
  declare function normalizeMathMarkdownDelimiters(markdown: string): string;
19
20
  declare const MATH_MARKDOWN_RULES_APPENDIX = "Math formatting rules (must follow):\n- Inline math: use single-dollar delimiters like $...$.\n- Display math: use $$ delimiters on their own lines with nothing else on those lines.\n- Do not use \\(...\\) or \\[...\\] delimiters.\n- Do not place display-math $$...$$ inside bullets or table cells; use inline $...$ there.\n- Escape non-math currency dollars as \\$.";
package/dist/index.js CHANGED
@@ -883,6 +883,7 @@ function renderMarkdownToHtml(markdown, options) {
883
883
  }
884
884
  var MarkdownRenderer = ({
885
885
  markdown,
886
+ className,
886
887
  onRunCode,
887
888
  executableLanguages = ["python", "r"]
888
889
  }) => {
@@ -1064,7 +1065,7 @@ var MarkdownRenderer = ({
1064
1065
  alive = false;
1065
1066
  };
1066
1067
  }, [html]);
1067
- return /* @__PURE__ */ jsx("div", { ref: containerRef, dangerouslySetInnerHTML: { __html: html } });
1068
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className, dangerouslySetInnerHTML: { __html: html } });
1068
1069
  };
1069
1070
  var markdown_renderer_default = MarkdownRenderer;
1070
1071
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@affanhamid/markdown-renderer",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Custom markdown renderer with KaTeX support",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",