All skills
onmax avatar

/comark

@de09c7b official
by Maxonmax/nuxt-skills715 stars
38

Comark (Components in Markdown) parser: syntax, AST, Vue/React/Svelte/Angular renderers, plugins, and LLM streaming with auto-close.

Use this Skill: https://skilld.dev/gh/onmax/nuxt-skills/comark

This session only. Nothing lands on disk.

referencesrendering-react.md

≈2.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

React Rendering Guide

Complete guide for rendering Markdown documents in React applications.

Table of Contents


Basic Usage

Use the Markdown component to render markdown:

import { Markdown } from '@comark/react'

const content = `
# Hello World

This is **markdown** content.

::alert{type="info"}
Important message
::
`

export default function App() {
  return <Markdown>{content}</Markdown>
}

Custom Components

Map custom React components to Comark elements:

import { Markdown } from '@comark/react'
import CustomHeading from './CustomHeading'
import CustomAlert from './CustomAlert'
import CustomCard from './CustomCard'

const customComponents = {
  h1: CustomHeading,
  h2: CustomHeading,
  alert: CustomAlert,
  card: CustomCard,
}

export default function App({ content }) {
  return (
    <Markdown
      components={customComponents}
    >{content}</Markdown>
  )
}

Custom Component Example

// CustomHeading.tsx
import React from 'react'

interface Props {
  __node: any  // Comark node
  id?: string
  children: React.ReactNode
}

export default function CustomHeading({ __node, id, children }: Props) {
  const tag = __node[0]  // h1, h2, etc.
  const Component = tag as keyof JSX.IntrinsicElements

  return (
    <Component id={id} className="custom-heading">
      {children}
    </Component>
  )
}

Styled Component:

import styled from 'styled-components'

const StyledHeading = styled.h1<{ level: number }>`
  font-family: 'Inter', sans-serif;
  font-weight: 700;
  font-size: ${props => 3 - props.level * 0.25}rem;
  margin-bottom: 1rem;
  color: #1a202c;
`

export default function CustomHeading({ __node, children }: Props) {
  const tag = __node[0]
  const level = parseInt(tag[1])  // Extract level from 'h1', 'h2', etc.

  return (
    <StyledHeading as={tag} level={level}>
      {children}
    </StyledHeading>
  )
}

Alert Component Example

// CustomAlert.tsx
import React from 'react'
import './Alert.css'

interface AlertProps {
  type?: 'info' | 'warning' | 'error' | 'success'
  children: React.ReactNode
}

export default function CustomAlert({ type = 'info', children }: AlertProps) {
  const icons = {
    info: 'ℹ️',
    warning: '⚠️',
    error: '❌',
    success: '✅',
  }

  return (
    <div className={`alert alert-${type}`} role="alert">
      <div className="alert-icon">{icons[type]}</div>
      <div className="alert-content">{children}</div>
    </div>
  )
}

Dynamic Component Resolution

Load components dynamically using componentsManifest:

import { Markdown } from '@comark/react'

const componentMap = {
  'alert': () => import('./Alert'),
  'card': () => import('./Card'),
  'button': () => import('./Button'),
}

async function loadComponent(name: string) {
  if (componentMap[name]) {
    return componentMap[name]()
  }
  throw new Error(`Component ${name} not found`)
}

export default function App({ content }) {
  return (
    <Markdown
      componentsManifest={loadComponent}
    >{content}</Markdown>
  )
}

Props Conversion

React renderer handles HTML attribute conversion automatically:

Attribute Mapping

// Markdown attribute → React prop
{class="foo"}           → className="foo"
{tabindex="0"}          → tabIndex={0}
{style="..."}           → style={{...}}  (converted to object)
{:bool="true"}          → bool={true}    (parsed from string)
{:count="5"}            → count={5}      (parsed as number)
{:data='{"key":"val"}'} → data={{key:"val"}} (parsed as object)

Style Conversion

// Input: style="color: red; font-size: 16px"
// Output: style={{ color: 'red', fontSize: '16px' }}

// Custom CSS properties preserved:
// Input: style="--custom-color: blue"
// Output: style={{ '--custom-color': 'blue' }}

Boolean Props

// Markdown: **text**{:disabled}
// React prop: disabled={true}

// Markdown: [link](url){:external}
// React prop: external={true}

Number Props

// Markdown: ::component{:count="5"}
// React prop: count={5}

// Markdown: ::component{:max="100"}
// React prop: max={100}

Object/Array Props

// Markdown: ::component{:config='{"theme":"dark"}'}
// React prop: config={{theme:"dark"}}

// Markdown: ::component{:items='["a","b","c"]'}
// React prop: items={["a","b","c"]}

Streaming Mode

Use the Markdown component with reactive state for streaming content:

import { useState, useEffect } from 'react'
import { Markdown } from '@comark/react'

export default function StreamingContent() {
  const [content, setContent] = useState('')
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    async function loadContent() {
      const response = await fetch('/api/content.md')
      const reader = response.body!.getReader()
      const decoder = new TextDecoder()

      while (true) {
        const { done, value } = await reader.read()
        if (done) break
        setContent(prev => prev + decoder.decode(value))
      }

      setIsLoading(false)
    }

    loadContent()
  }, [])

  return (
    <>
      {isLoading && <div>Loading...</div>}
      <Markdown>{content}</Markdown>
    </>
  )
}

Prose Components

The Markdown component uses built-in prose styling automatically. You can override with custom components:

import { Markdown } from '@comark/react'
import CustomAlert from './CustomAlert'

const components = {
  alert: CustomAlert,  // Override or add custom components
}

export default function App({ content }) {
  return <Markdown components={components}>{content}</Markdown>
}

Tailwind CSS Prose

import { Markdown } from '@comark/react'

export default function App({ content }) {
  return (
    <article className="prose prose-lg dark:prose-dark max-w-none">
      <Markdown>{content}</Markdown>
    </article>
  )
}

Custom Props Handling

Access props in custom components:

// CustomAlert.tsx
interface AlertProps {
  type?: string        // From {type="info"}
  bool?: boolean       // From {bool} → :bool="true"
  count?: number       // From {:count="5"}
  data?: object        // From {:data='{"key":"val"}'}
  __node?: any         // Original Comark node
  children: React.ReactNode
}

export default function CustomAlert({
  type = 'info',
  bool,
  count,
  data,
  __node,
  children
}: AlertProps) {
  return (
    <div
      className={`alert alert-${type}`}
      data-bool={bool}
      data-count={count}
      data-info={JSON.stringify(data)}
      role="alert"
    >
      {children}
    </div>
  )
}

Property Types

  • :bool="true" → bool={true} (boolean)
  • :count="5" → count={5} (number)
  • :obj='{"key":"val"}' → obj={{key:"val"}} (object)
  • attr="value" → attr="value" (string)

Accessing Node Structure

interface Props {
  __node?: any
  children: React.ReactNode
}

export default function Component({ __node, children }: Props) {
  // Node structure: [tag, props, ...children]
  const tag = __node?.[0]
  const nodeProps = __node?.[1] || {}
  const nodeChildren = __node?.slice(2) || []

  return (
    <div data-tag={tag}>
      {children}
    </div>
  )
}

Working with Complex Props

// DataTable.tsx
interface DataTableProps {
  columns?: string[]   // From {:columns='["Name","Age"]'}
  sortable?: boolean   // From {sortable}
  striped?: boolean    // From {striped}
  children: React.ReactNode
}

export default function DataTable({
  columns = [],
  sortable = false,
  striped = false,
  children
}: DataTableProps) {
  return (
    <table className={striped ? 'table-striped' : ''}>
      {columns.length > 0 && (
        <thead>
          <tr>
            {columns.map((col, i) => (
              <th key={i}>
                {col}
                {sortable && <button>↕</button>}
              </th>
            ))}
          </tr>
        </thead>
      )}
      <tbody>{children}</tbody>
    </table>
  )
}

Usage in Markdown:

::data-table{:columns='["Name", "Age", "Email"]' sortable striped}
Table content here
::

CSS Class Name

Add custom wrapper class:

<Markdown
  className="prose dark:prose-dark"
>{content}</Markdown>

With Tailwind CSS

<Markdown
  className="prose prose-slate lg:prose-xl dark:prose-invert max-w-none"
>{content}</Markdown>

← Back to Main Skills Guide

Source: SKILL.md on GitHub

No alerts9d3 checks · Risk SAFE
  • Gen Agent Trust Hub9d

    Comark is a markdown parsing and rendering library that enables custom components in markdown across various web frameworks. The skill is rated as low risk because its primary function involves processing untrusted content (e.g., from AI agents), creating a surface for indirect prompt injection. It also utilizes dynamic component loading, which is a core feature but represents a dynamic execution pattern.

  • Socket9d

    No alerts

  • Snyk9d

    Risk: LOW · No issues

Signed by skilld at de09c7b. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated last week

README badge

README badge for onmax/nuxt-skills/comark