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-vue.md

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

Vue Rendering Guide

Complete guide for rendering Markdown documents in Vue applications.

Table of Contents


Basic Usage

Use the Markdown component to render markdown:

<template>
  <Markdown>{{ content }}</Markdown>
</template>

<script setup lang="ts">
import { Markdown } from '@comark/vue'

const content = `
# Hello World

This is **markdown** content.

::alert{type="info"}
Important message
::
`
</script>

Custom Components

Map custom Vue components to Comark elements:

<template>
  <Markdown :components="customComponents">{{ content }}</Markdown>
</template>

<script setup lang="ts">
import { Markdown } from '@comark/vue'
import CustomHeading from './CustomHeading.vue'
import CustomAlert from './CustomAlert.vue'
import CustomCard from './CustomCard.vue'

const customComponents = {
  h1: CustomHeading,
  h2: CustomHeading,
  alert: CustomAlert,
  card: CustomCard,
}
</script>

Custom Component Example

<!-- CustomHeading.vue -->
<template>
  <component :is="tag" :id="id" class="custom-heading">
    <slot />
  </component>
</template>

<script setup lang="ts">
import { computed } from 'vue'

const props = defineProps<{
  __node: any  // Comark node
}>()

const tag = computed(() => props.__node[0])
const id = computed(() => props.__node[1]?.id)
</script>

<style scoped>
.custom-heading {
  font-family: 'Inter', sans-serif;
  font-weight: 700;
  margin-bottom: 1rem;
}
</style>

Alert Component Example

<!-- CustomAlert.vue -->
<template>
  <div :class="`alert alert-${type}`" role="alert">
    <div class="alert-icon">
      <Icon :name="iconName" />
    </div>
    <div class="alert-content">
      <slot />
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

const props = defineProps<{
  type?: 'info' | 'warning' | 'error' | 'success'
  __node?: any
}>()

const iconName = computed(() => {
  switch (props.type) {
    case 'info': return 'info-circle'
    case 'warning': return 'exclamation-triangle'
    case 'error': return 'times-circle'
    case 'success': return 'check-circle'
    default: return 'info-circle'
  }
})
</script>

<style scoped>
.alert {
  display: flex;
  padding: 1rem;
  border-radius: 0.5rem;
  margin-bottom: 1rem;
}

.alert-info {
  background-color: #e3f2fd;
  color: #1976d2;
}

.alert-warning {
  background-color: #fff3e0;
  color: #f57c00;
}
</style>

Dynamic Component Resolution

Load components dynamically using componentsManifest:

<template>
  <Markdown
    :components-manifest="loadComponent"
  >{{ content }}</Markdown>
</template>

<script setup lang="ts">
import { Markdown } from '@comark/vue'

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

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

Slots Support

Comark components with slots work seamlessly in Vue:

Markdown with Slots

::card
#header
## Card Title

#content
Main content here with **markdown** support

#footer
Footer text
::

Custom Component with Slots

<!-- Card.vue -->
<template>
  <div class="card">
    <div v-if="$slots.header" class="card-header">
      <slot name="header" />
    </div>
    <div class="card-content">
      <slot name="content" />
      <!-- Default slot as fallback -->
      <slot />
    </div>
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>

<style scoped>
.card {
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  overflow: hidden;
}

.card-header {
  background-color: #f9fafb;
  padding: 1rem;
  border-bottom: 1px solid #e5e7eb;
}

.card-content {
  padding: 1rem;
}

.card-footer {
  background-color: #f9fafb;
  padding: 1rem;
  border-top: 1px solid #e5e7eb;
}
</style>

Nested Slots

::tabs
#tab1
### First Tab
Content for tab 1

#tab2
### Second Tab
Content for tab 2
::
<!-- Tabs.vue -->
<template>
  <div class="tabs">
    <div class="tab-headers">
      <button
        v-for="(slot, name) in $slots"
        :key="name"
        :class="{ active: activeTab === name }"
        @click="activeTab = name"
      >
        {{ name }}
      </button>
    </div>
    <div class="tab-content">
      <component :is="() => $slots[activeTab]?.()" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const activeTab = ref('tab1')
</script>

Streaming Mode

The Markdown component can be used with reactive content for streaming scenarios:

<template>
  <div>
    <Markdown>{{ content }}</Markdown>
    <div v-if="isLoading">Loading...</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Markdown } from '@comark/vue'

const content = ref('')
const isLoading = ref(true)

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
    content.value += decoder.decode(value)
  }

  isLoading.value = false
}

loadContent()
</script>


Prose Components

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

<template>
  <Markdown :components="components">{{ content }}</Markdown>
</template>

<script setup lang="ts">
import { Markdown } from '@comark/vue'
import CustomAlert from './CustomAlert.vue'

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

Error Handling

The MarkdownDocument component has built-in error capture via Vue's onErrorCaptured hook. Component rendering errors are caught automatically without crashing the application. You can also use Vue's native onErrorCaptured in a parent component to handle errors:

<template>
  <Markdown :value="content" />
</template>

<script setup lang="ts">
import { onErrorCaptured } from 'vue'
import { Markdown } from '@comark/vue'

onErrorCaptured((error) => {
  console.error('Component error:', error)
  return false // prevent propagation
})
</script>

Props Access

Custom components receive the original Comark node and parsed props:

<!-- CustomAlert.vue -->
<template>
  <div :class="alertClasses" role="alert">
    <slot />
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

const props = defineProps<{
  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
}>()

const alertClasses = computed(() => [
  'alert',
  `alert-${props.type || 'info'}`,
  { 'alert-important': props.bool }
])
</script>

Property Parsing Rules

  • Props starting with : are parsed as booleans/JSON
  • Standard HTML attributes work normally
  • __node provides access to the raw AST node

Accessing Node Structure

<script setup lang="ts">
const props = defineProps<{ __node?: any }>()

// Node structure: [tag, props, ...children]
const tag = computed(() => props.__node?.[0])
const nodeProps = computed(() => props.__node?.[1] || {})
const children = computed(() => props.__node?.slice(2) || [])
</script>

Working with Complex Props

<!-- DataTable.vue -->
<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns" :key="col">{{ col }}</th>
      </tr>
    </thead>
    <tbody>
      <slot />
    </tbody>
  </table>
</template>

<script setup lang="ts">
const props = defineProps<{
  columns?: string[]  // From {:columns='["Name","Age"]'}
  sortable?: boolean  // From {sortable}
  __node?: any
}>()
</script>

Usage in Markdown:

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

← 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