Jewel Markdown
SkillDocs & knowledgeHelps your agent build or debug Jewel Markdown parsing, rendering, and styling code.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Jewel Markdown skill
About this capability
Build or debug Jewel Markdown parsing, rendering, and styling.
What this skill tells your AI
The instructions your AI receives, as published by jetbrains/intellij-community in .agents/skills/jewel-markdown/SKILL.md and read by ahel’s review.
Jewel Markdown is a two-stage renderer: parse raw Markdown into Jewel's MarkdownBlock / InlineMarkdown model, then render that model
with Jewel Compose renderers. Choose the smallest customization point that matches the job.
The Jewel Markdown sources live in JetBrains intellij-community under platform/jewel/markdown/. Confirm exact API/file/symbol names by reading those sources (grep/read) rather than trusting memory before proposing code. For deeper topic detail, read the matching reference only when that topic is in play:
references/CODE-HIGHLIGHTING.md— fenced/indented code highlighting,CodeHighlighter, why code is unstyled by default.references/IMAGE-LOADING.md—ImageRendererExtension+ImageSourceResolver, Coil3, path resolution.references/SCROLL-SYNC.md— editor-preview scroll synchronization and itsScrollState-only limitation.references/HTML-PARSING.md— embedded HTML, built-in tag conversion,MarkdownHtmlConverterExtension, alignment.
First decision: what are you changing?
- Visual styling only — customize
MarkdownStylingand theme-specific styling helpers. - Existing block UI behavior — use a custom
MarkdownBlockRenderer, usually by subclassingDefaultMarkdownBlockRendererand overriding the specificRender*method. - Existing inline text behavior — use a custom
InlineMarkdownRenderer, usually by subclassingDefaultInlineMarkdownRenderer. - New Markdown syntax — write a processor extension and a renderer extension.
- Images — use an
ImageRendererExtension, not a general block renderer. - Embedded HTML — enable
parseEmbeddedHtmlon theMarkdownProcessor, and for custom HTML→Markdown mapping add aMarkdownHtmlConverterExtension. - A live editor preview — use
MarkdownMode.EditorPreviewwith a dedicatedMarkdownProcessorinstance and render withLazyMarkdown.
Do not start with a custom renderer when styling or an extension solves the problem.
High-level rendering flow
MarkdownProcessorparses raw Markdown intoList<MarkdownBlock>.- Blocks may contain
InlineMarkdownnodes. Markdownrenders short/simple content in aColumn;LazyMarkdownrenders larger or editor-preview content in aLazyColumn.MarkdownBlockRendererrenders block nodes and delegates inline content toInlineMarkdownRenderer.ProvideMarkdownStylingwiresLocalMarkdownStyling,LocalMarkdownProcessor,LocalMarkdownBlockRenderer, and code/image support intoJewelTheme.- Extensions can plug into processing, block rendering, and inline rendering (with some limitations).
Prefer processing Markdown outside composition for non-trivial or frequently changing text. The string-taking Markdown(...) overload is
convenient, but it parses from composition and is best for small, mostly static snippets.
Built-in and existing extension choices
Use the existing extension modules before inventing syntax:
- Plain URLs are not becoming links: add
AutolinkProcessorExtensionto theMarkdownProcessor. - GFM tables: add
GitHubTableProcessorExtensionandGitHubTableRendererExtension. The processor extension also bundles aMarkdownHtmlConverterExtensionfor<table>(active whenparseEmbeddedHtml = true). - GFM strikethrough: add
GitHubStrikethroughProcessorExtensionandGitHubStrikethroughRendererExtension. The processor extension also bundles aMarkdownHtmlConverterExtensionfor<s>,<strike>, and<del>(active whenparseEmbeddedHtml = true). - GFM alerts: add
GitHubAlertProcessorExtensionandGitHubAlertRendererExtension. - Images: add an
ImageRendererExtension, commonlyCoil3ImageRendererExtension, to the renderer extensions and provide an image source resolver where needed. For details readreferences/IMAGE-LOADING.md. - Code syntax highlighting: in a plugin, use the
Project-aware bridgeProvideMarkdownStyling, which wires an IJPL-backed highlighter by default; in standalone (or a bridge overload without aProject) the default is a no-op and you must provide aCodeHighlighter. For details readreferences/CODE-HIGHLIGHTING.md.
For a worked example of combining several extensions (processor + renderer + theme-specific styling, parsing off composition, LazyMarkdown), read the standalone sample at platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt.
Keep parser and renderer extensions paired when a feature has both sides. If a processor creates a custom block or inline node and no renderer extension claims it, the content will not render usefully. Do not invent renderer extensions for parse-only features such as autolink, and do not invent processor extensions for renderer-only image loading.
Embedded HTML
HTML support is off by default: a MarkdownProcessor ignores embedded HTML unless you construct it with parseEmbeddedHtml = true. There
are two distinct HTML paths, do not conflate them:
- Native HTML mapped to Markdown: when
parseEmbeddedHtmlis on, a fixed set of built-in tags is converted into normalMarkdownBlocks (p,li,ol,ul,h1-h6,code,pre,img). These render through the standard block/inline renderers, not through a raw-HTML renderer. - Custom tag conversion: to map an additional HTML tag onto a Markdown block your code understands (e.g.,
<table>to a tables block), implement aMarkdownHtmlConverterExtensionand expose it from yourMarkdownProcessorExtension.htmlConverterExtension. It declaressupportedTagsand returns anHtmlElementConverterper tag; the converter turns an HTML element into aMarkdownBlock(or inline list), delegating children/inlines via the provided lambdas. Existing GFM extensions ship these:gfm-tablesconverts<table>(block), andgfm-strikethroughconverts<s>/<strike>/<del>(inline). Use them as references and add the extension whose tags you need rather than reimplementing it.
For full details, models, and wiring, read references/HTML-PARSING.md.
Key points and gotchas:
htmlConverterExtensions are only collected whenparseEmbeddedHtml = true; adding one without enabling embedded HTML does nothing.- HTML that is not converted is preserved as an
HtmlBlock(raw HTML string) or inline HTML; the default renderer shows it via thehtmlBlockstyling, it is not interpreted as native UI. Jewel does not do general inline-HTML rendering. - HTML carrying layout attributes (e.g., an
alignattribute) becomes aMarkdownBlock.HtmlBlockWithAttributeswrapping the converted block; the default renderer reads thealignattribute and propagates text alignment to children. A custom renderer that overrides this path must preserve that alignment behavior (see Gotchas for custom renderers). - Custom HTML conversion is parse-side only. If a converter emits a custom block type, you still need a matching renderer extension to display it.
Live Markdown editor/preview
MarkdownMode.EditorPreview optimizes for small, frequent edits (a user typing): the MarkdownProcessor keeps state between calls and
re-parses only the block(s) that changed instead of the whole document. That statefulness is why an editor-preview processor is tied to one
evolving document and must not be shared across unrelated documents (doing so busts the cache and is slower than Standalone). Use
MarkdownMode.Standalone (the default, stateless) for content that is parsed once and doesn't change.
For a side-by-side editor and preview:
- Keep raw text/editing state in the presenter or state holder, not hidden inside the preview leaf.
- Create one
MarkdownProcessor(markdownMode = MarkdownMode.EditorPreview(scrollingSynchronizer = ...))per editable document/preview stream. Do not share editor-preview processors across unrelated documents. - Process edits off the UI path when practical, debounce/drop stale work if the editor can emit faster than rendering.
- Render with
LazyMarkdownfor documents and previews. - Add the expected dialect extensions: autolink is processor-only, tables/strikethrough/alerts need processor and renderer pieces, and images need an image renderer extension.
- If scroll sync matters, use the scrolling synchronizer/rendering support rather than ad-hoc list scrolling. Read
references/SCROLL-SYNC.md— note the synchronizer currently supportsScrollState, notLazyListState.
AI chat with Markdown
For chat timelines, do not make Markdown own the chat architecture.
Use a presenter/coordinator to project message state, roles, streaming status, and parsed Markdown blocks. The UI should render a
LazyColumn of message rows and use Markdown or LazyMarkdown inside text-bearing rows as appropriate. Keep networking, agent protocol,
message mutation, and Markdown parsing policy outside leaf composables. Use stable lazy-list keys and content types for heterogeneous chat
rows.
Custom syntax: extension or renderer?
Write a custom extension when the Markdown input language changes, e.g., task list items, custom admonitions, ::file:/path/foo.kt::, or
another syntax that must parse into a domain model.
Write a custom renderer when the existing parsed model is right, but the UI should change, e.g., headings need link buttons, code blocks need copy buttons, list markers need different layout, or inline images need product-specific treatment.
Many features need both: an extension to parse syntax into model nodes, and a renderer to display those nodes. Some may need more than two parts. See the examples below.
How to write a custom block extension
Use gfm-alerts and gfm-tables as templates.
- Define a model type implementing
MarkdownBlock.CustomBlock. - Add a
MarkdownProcessorExtension. - If CommonMark needs help recognizing the syntax, expose a CommonMark
ParserExtension. - Expose a
MarkdownBlockProcessorExtensionthat checkscanProcess(CustomBlock)and returns yourMarkdownBlock.CustomBlockfromprocessMarkdownBlock. - Set
allowsMergingWithNextBlock = trueonly when incremental editor parsing must reparse a following block that can merge into this one, as tables do. - Add a
MarkdownRendererExtensionexposing aMarkdownBlockRendererExtension. - In
RenderCustomBlock, render native Jewel Compose UI and delegate nested Markdown back to the providedMarkdownBlockRenderer/InlineMarkdownRendererwhere possible. - Add standalone and bridge styling helpers when the feature has colors, spacing, borders, or icons.
- If the block will be used in an editor preview and should be a scroll-sync target, opt in: the scroll-sync renderer does not wrap custom
blocks automatically. Wrap your content in
AutoScrollableBlockinRenderCustomBlock. Seereferences/SCROLL-SYNC.md. - Add processor tests and renderer tests or validated sample coverage.
How to write a custom delimited inline extension
Use gfm-strikethrough as the template.
- Define a model type implementing
InlineMarkdown.CustomDelimitedNode. - Add a
MarkdownProcessorExtensionwith a CommonMark parser extension if needed. - Expose a
MarkdownDelimitedInlineProcessorExtensionthat turns supported CommonMarkDelimitednodes into your custom inline node. - Add a
MarkdownRendererExtensionexposingMarkdownDelimitedInlineRendererExtension. - Render to an
AnnotatedString, recursively delegating nested inline content to the suppliedInlineMarkdownRenderer. - Add an HTML converter only if embedded HTML should map to the same inline model.
Example: inline icons and rich inline content
MarkdownDelimitedInlineRendererExtension returns only an AnnotatedString. If custom inline syntax must show an icon or other
InlineTextContent — for example ::file:/my/path/foo.kt:: with the file icon and name — plan for an extension encompassing parsing plus
renderer customization:
- Parse the syntax into a custom inline node.
- Provide an inline renderer or block renderer path that appends inline-content placeholders for those nodes.
- Override the block rendering for text-bearing blocks (
Paragraph, headings, table cells, or whichever contexts you support) so the JewelTextreceives the matchinginlineContentmap. - Use Jewel icon APIs (
IconKey,AllIconsKeys, or platform file-type icon APIs in bridge/plugin context) inside theInlineTextContent. - Document supported contexts rather than pretending every Markdown block can host the custom inline content automatically.
Example: GFM task lists
Do not claim Jewel already supports GFM task lists. They are listed as missing/roadmap in Jewel docs. To support - [ ] / - [x] task list
items, create and validate a new extension:
- Decide whether the model is a custom block/list item wrapper or an extension of list rendering.
- Parse checked/unchecked state into an explicit model.
- Render with native Jewel checkbox-like visuals, preserving Markdown semantics and disabled/read-only behavior as appropriate.
- Validate both processor output and rendering. Include editor-preview incremental parsing if task-list syntax can interact with neighboring list blocks.
- If you're rendering interactive components such as checkboxes, make sure their status is managed, and that changes are reflected in the
underlying Markdown (e.g., checked state for a checkbox should manifest as
[ ]or[x]in text).
Gotchas for custom renderers
When overriding or replacing rendering, preserve the behaviors the default renderer relies on. Each item below is something a custom renderer can silently break.
- Text alignment: every text-bearing block in
DefaultMarkdownBlockRendererpassestextAlign = LocalTextAlignment.currentto its JewelText. That composition local is set when an aligned block (e.g., an HTML block with analignattribute) wraps its children, so alignment propagates into nested text. A customRender*override or a renderer written from scratch that emitsTextwithout forwarding the current text alignment will silently lose center/right alignment. Always apply the provided text alignment in custom text rendering. NoteLocalTextAlignmentis internal toDefaultMarkdownBlockRenderer; prefer subclassing it (and still forwardingtextAlignin your override) rather than reimplementing block rendering and dropping this wiring. - Content color: text-bearing rendering resolves color via the styling text style falling back to
LocalContentColor. Preserve that fallback instead of hardcoding a color. - Inline content / images: paragraphs that contain images render through an
inlineContentmap. If you override paragraph or other text-bearing rendering, keep passing theinlineContentmap so images and other inline placeholders still appear. - Enabled/disabled state: honor the
enabledflag (links non-interactive, disabled styling) rather than always rendering the enabled appearance. - Delegation: render nested blocks/inlines through the provided
MarkdownBlockRenderer/InlineMarkdownRendererso styling, extensions, and these behaviors stay consistent. - Editor-preview scroll sync: scroll sync is implemented entirely by
ScrollSyncMarkdownBlockRenderer(aDefaultMarkdownBlockRenderersubclass that wraps blocks inAutoScrollableBlockand reports layout). If you supply your own block renderer for an editor preview, you bypass that subclass and lose scroll sync. SubclassScrollSyncMarkdownBlockRendererinstead ofDefaultMarkdownBlockRenderer(callingsuperfrom your overrides), or re-implement theAutoScrollableBlockwiring yourself. Seereferences/SCROLL-SYNC.md.
General rule: when you override one Render* method, mirror the default implementation's wiring (alignment, color, inline content, enabled
state, and editor-preview scroll sync) and change only what you intend to change.
Validation checklist
Before finishing Jewel Markdown work:
- Confirm runtime context: standalone uses int-ui standalone styling; IntelliJ plugin/bridge uses IDE LaF bridge styling.
- Confirm required processor and renderer extensions are both wired.
- Confirm
MarkdownProcessoreditor-preview instances are not shared across unrelated documents. - Confirm heavy parsing is not happening in a hot
LazyColumnitem body. - Confirm code highlighting, image loading, and URL click handling are provided where the UX requires them.
- Confirm custom syntax has tests for parsing and rendering; for new extensions, use existing extension tests as guides.
- Confirm task lists or other missing GFM features were implemented/validated, not merely mentioned as supported.
- Confirm custom text rendering forwards
LocalTextAlignment(text alignment), preserves the content-color fallback, keeps theinlineContentmap, and honorsenabled. - Confirm HTML expectations: embedded HTML needs
parseEmbeddedHtml = true; custom tag mapping needs aMarkdownHtmlConverterExtension; and unconverted/inline HTML is shown as raw text, not interpreted as native UI.
Signals
- GitHub stars
- 21k
- Forks
- 6k
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
jewel-markdown- Source
- github.com/jetbrains/intellij-community