Introduction

Beautiful typography components and utilities to style your content with Pohon UI.

Pohon UI provides beautifully styled Prose components for your markdown content. Whether you render markdown with a library or use prose components directly, Pohon UI applies its design system so your content automatically adapts to your application's theme.

Prose components are automatically enabled when using @nuxtjs/mdc, @nuxt/content or @comark/nuxt.

Otherwise, enable the prose option in your nuxt.config.ts:
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['pohon-ui'],
  ui: {
    prose: true
  }
});
For Nuxt Content installation and setup, check the Content integration guide.
To use prose components with Vue, you need to enable the prose option in your vite.config.ts:
vite.config.ts
import vue from '@vitejs/plugin-vue';
import ui from 'pohon-ui/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    vue(),
    ui({
      prose: true
    })
  ]
});

Usage

There are multiple ways to render styled content:

  1. Markdown rendering: use ContentRenderer, MDC, Markdown, or MarkdownDocument to render markdown with automatic prose styling
  2. Direct Vue usage: import and use prose components directly in your Vue templates

There are multiple ways to render styled content:

  1. Markdown rendering: use Markdown or MarkdownDocument to render markdown with automatic prose styling
  2. Direct Vue usage: import and use prose components directly in your Vue templates

With ContentRenderer Nuxt only

When using @nuxt/content, render your collection pages with the <ContentRenderer> component:

pages/[...slug].vue
<script setup lang="ts">
const route = useRoute();

const { data: page } = await useAsyncData(route.path, () => queryCollection('docs').path(route.path).first());
</script>

<template>
  <ContentRenderer :value="page" />
</template>

With MDC component Nuxt only

When using @nuxtjs/mdc or @nuxt/content, use the <MDC> component to render markdown strings:

<script setup lang="ts">
const value = `# Hello World

Learn more about the **MDC** component in the [documentation](https://github.com/nuxt-content/mdc).
`;
</script>

<template>
  <MDC :value="value" />
</template>
@nuxtjs/mdc is being deprecated in favor of Comark. Your existing markdown files are compatible, no content changes needed.

With Markdown component

Comark renders markdown with built-in support for streaming. It incrementally renders tokens as they arrive, making it ideal for AI chat responses and any other dynamic markdown content.

<script setup lang="ts">
import shiki from '@comark/nuxt/plugins/shiki';

const plugins = [shiki()];

const markdown = ref('# Hello World\n\nThis is **streaming** markdown.');
</script>

<template>
  <Markdown
    :value="markdown"
    :plugins="plugins"
  />
</template>
<script setup lang="ts">
import { Markdown } from '@comark/vue';
import shiki from '@comark/vue/plugins/shiki';
import { ref } from 'vue';

const plugins = [shiki()];

const markdown = ref('# Hello World\n\nThis is **streaming** markdown.');
</script>

<template>
  <Markdown
    :value="markdown"
    :plugins="plugins"
  />
</template>
The shiki plugin adds syntax highlighting to code blocks. Use the :streaming prop when rendering content that arrives incrementally, such as AI responses.To support dark mode, add the following CSS to your stylesheet:
main.css
html.dark .shiki span {
  color: var(--shiki-dark) !important;
  background-color: var(--shiki-dark-bg) !important;
  font-style: var(--shiki-dark-font-style) !important;
  font-weight: var(--shiki-dark-font-weight) !important;
  text-decoration: var(--shiki-dark-text-decoration) !important;
}
See the Chat component for a full example of using Comark to render AI chat messages.

With MarkdownDocument

Use the <MarkdownDocument> component to render a pre-parsed MarkdownDocument without shipping any parser or plugin code to the browser. This is useful when parsing happens on the server, in a build step, or via an API.

<script setup lang="ts">
const { data: tree } = await useFetch('/api/content');
</script>

<template>
  <MarkdownDocument
    v-if="tree"
    :value="tree"
  />
</template>
<script setup lang="ts">
import { MarkdownDocument } from '@comark/vue';
import { onMounted, ref } from 'vue';

const tree = ref();

onMounted(async () => {
  tree.value = await fetch('/api/content').then((r) => r.json());
});
</script>

<template>
  <MarkdownDocument
    v-if="tree"
    :value="tree"
  />
</template>

With Prose components

Use prose components directly in Vue templates for maximum control:

<template>
  <ProseTable>
    <ProseThead>
      <ProseTr>
        <ProseTh>Prop</ProseTh>
        <ProseTh>Default</ProseTh>
      </ProseTr>
    </ProseThead>
    <ProseTbody>
      <ProseTr>
        <ProseTd>
          <ProseCode>color</ProseCode>
        </ProseTd>
        <ProseTd>
          <ProseCode>neutral</ProseCode>
        </ProseTd>
      </ProseTr>
    </ProseTbody>
  </ProseTable>
</template>

With explicit imports

Prose components are also exported from pohon-ui/components/prose. Import the ones you need, or all of them, and pass them to the components prop of <Markdown> or <MarkdownDocument>:

<script setup lang="ts">
import * as proseComponents from 'pohon-ui/components/prose';

const markdown = ref('# Hello World');
</script>

<template>
  <Markdown
    :value="markdown"
    :components="proseComponents"
  />
</template>
<script setup lang="ts">
import { Markdown } from '@comark/vue';
import * as proseComponents from 'pohon-ui/components/prose';
import { ref } from 'vue';

const markdown = ref('# Hello World');
</script>

<template>
  <Markdown
    :value="markdown"
    :components="proseComponents"
  />
</template>
The prose option still needs to be enabled, it generates the theme of the prose components.

MDC Syntax

Markdown elements are automatically mapped to prose components: # Heading becomes <ProseH1>, **bold** becomes <ProseStrong>, `code` becomes <ProseCode>, and so on.

MDC Syntax goes further by letting you use Vue components directly inside markdown. It is supported by Nuxt Content, Nuxt MDC and Comark.

Regular markdown with bold and italic text.

Use MDC components for rich interactions!

Use pnpm add pohon-ui to install

pnpm add pohon-ui
Regular markdown with **bold** and *italic* text.

::callout{icon="i-lucide-rocket" color="primary"}
Use MDC components for rich interactions!
::

::tabs

:::tabs-item{label="Installation"}
Use pnpm add pohon-ui to install
:::

:::tabs-item{label="Usage"}
Import components and use them in your templates
:::

::

::code-group

```bash [pnpm]
pnpm add pohon-ui
```

```bash [yarn]
yarn add pohon-ui
```

```bash [npm]
npm install pohon-ui
```

```bash [bun]
bun add pohon-ui
```

::

Theme

Override any prose component styling in your app configuration:

app/app.config.ts
export default defineAppConfig({
  ui: {
    prose: {
      h1: {
        slots: {
          base: 'scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl'
        }
      },
      p: {
        base: 'leading-7 [&:not(:first-child)]:mt-6'
      }
    }
  }
});
vite.config.ts
import vue from '@vitejs/plugin-vue';
import ui from 'pohon-ui/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        prose: {
          h1: {
            slots: {
              base: 'scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl'
            }
          },
          p: {
            base: 'leading-7 [&:not(:first-child)]:mt-6'
          }
        }
      }
    })
  ]
});