Components Reference

Content Components

Components for displaying rich text content, articles, and prose pages

Content components provide beautiful layouts for displaying rich text content from any source - the Crouton editor, Nuxt Content, external CMSs, or plain HTML strings.

CroutonContentPreview

A compact preview component for displaying truncated content in table cells or cards. Automatically strips HTML tags and shows a tooltip with the full content on hover.

Props

interface ContentPreviewProps {
  content?: string    // HTML content to preview
  limit?: number      // Character limit (default: 100)
}
PropTypeDefaultDescription
contentstring''HTML content to preview
limitnumber100Maximum characters before truncation

Basic Usage

<template>
  <!-- In a table cell -->
  <CroutonContentPreview :content="row.body" />

  <!-- With custom limit -->
  <CroutonContentPreview :content="post.excerpt" :limit="150" />
</template>

In Collection Tables

Perfect for displaying content fields in your collection list views:

<template>
  <CroutonCollection
    collection="posts"
    :columns="columns"
    :rows="posts"
  >
    <template #body-cell="{ row }">
      <CroutonContentPreview :content="row.original.body" :limit="80" />
    </template>
  </CroutonCollection>
</template>

CroutonContentPage

A generic content page wrapper with Tailwind Typography (prose) styling, optional table of contents, and flexible slot-based layout.

Props

interface TocLink {
  id: string
  text: string
  depth: number
  children?: TocLink[]
}

interface ContentPageProps {
  content?: string                    // HTML content
  title?: string                      // Page title
  description?: string                // Page description/subtitle
  toc?: boolean | TocLink[]           // Enable TOC or provide custom links
  maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'full'
}
PropTypeDefaultDescription
contentstring''HTML content to render
titlestringundefinedPage title
descriptionstringundefinedPage subtitle/description
tocboolean | TocLink[]falseAuto-generate TOC or provide custom links
maxWidthstring'3xl'Maximum content width

Slots

SlotDescription
headerCustom header content (replaces title/description)
defaultCustom content (replaces content prop rendering)
sidebarSidebar content (replaces auto-generated TOC)
footerFooter content below the main content

Basic Usage

<template>
  <CroutonContentPage
    title="About Us"
    description="Learn more about our company"
    :content="page.body"
  />
</template>

<script setup lang="ts">
const { data: page } = await useFetch('/api/pages/about')
</script>

With Table of Contents

Enable automatic TOC generation from headings in your content:

<template>
  <CroutonContentPage
    title="Documentation"
    :content="docs.body"
    toc
  />
</template>
Auto-generated TOC requires headings in your content to have id attributes. For example: <h2 id="getting-started">Getting Started</h2>

With Custom Sidebar

<template>
  <CroutonContentPage :content="page.body">
    <template #sidebar>
      <nav class="space-y-2">
        <h3 class="font-semibold">Related Pages</h3>
        <NuxtLink v-for="link in relatedPages" :to="link.to">
          {{ link.title }}
        </NuxtLink>
      </nav>
    </template>

    <template #footer>
      <div class="flex justify-between">
        <NuxtLink :to="prevPage">Previous</NuxtLink>
        <NuxtLink :to="nextPage">Next</NuxtLink>
      </div>
    </template>
  </CroutonContentPage>
</template>

With Custom Content

Use the default slot to render content yourself:

<template>
  <CroutonContentPage title="My Page">
    <div class="prose dark:prose-invert">
      <p>Custom rendered content here...</p>
      <MyCustomComponent />
    </div>
  </CroutonContentPage>
</template>

CroutonContentArticle

A full-featured article/blog post layout with title, author, date, featured image, tags, and reading time support.

Props

interface Author {
  name: string
  avatar?: string
  description?: string
}

interface ContentArticleProps {
  title: string                       // Article title (required)
  description?: string                // Excerpt/subtitle
  author?: Author                     // Author information
  date?: Date | string                // Publication date
  image?: string                      // Featured image URL
  imageAlt?: string                   // Featured image alt text
  content?: string                    // HTML content
  tags?: string[]                     // Article tags
  readingTime?: string                // Reading time (e.g., "5 min read")
  maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'
}
PropTypeDefaultDescription
titlestringrequiredArticle title
descriptionstringundefinedExcerpt or subtitle
authorAuthorundefinedAuthor name, avatar, and description
dateDate | stringundefinedPublication date
imagestringundefinedFeatured image URL
imageAltstringtitleFeatured image alt text
contentstringundefinedHTML content
tagsstring[]undefinedArticle tags
readingTimestringundefinedReading time display
maxWidthstring'3xl'Maximum content width

Slots

SlotDescription
headerCustom header (replaces title, author, date, tags)
defaultCustom content (replaces content prop rendering)
sidebarSticky sidebar content
footerFooter content (e.g., related posts, share buttons)

Basic Usage

<template>
  <CroutonContentArticle
    :title="post.title"
    :description="post.excerpt"
    :content="post.body"
    :date="post.publishedAt"
  />
</template>

<script setup lang="ts">
const { data: post } = await useFetch('/api/posts/my-post')
</script>

Full Blog Post

<template>
  <CroutonContentArticle
    :title="post.title"
    :description="post.excerpt"
    :author="{
      name: post.author.name,
      avatar: post.author.avatar,
      description: post.author.role
    }"
    :date="post.publishedAt"
    :image="post.featuredImage"
    :content="post.body"
    :tags="post.tags"
    reading-time="5 min read"
  >
    <template #sidebar>
      <TableOfContents :links="tocLinks" />
    </template>

    <template #footer>
      <ShareButtons :url="post.url" :title="post.title" />
      <RelatedPosts :posts="relatedPosts" />
    </template>
  </CroutonContentArticle>
</template>

With Editor Content

Display content created with CroutonEditorSimple:

<template>
  <CroutonContentArticle
    :title="article.title"
    :content="article.content"
    :date="article.createdAt"
  />
</template>

<script setup lang="ts">
// Content from CroutonEditorSimple is stored as HTML
const { data: article } = await useCollectionQuery('articles', {
  query: { id: route.params.id }
})
</script>

With Nuxt Content

Works seamlessly with @nuxt/content:

<template>
  <CroutonContentArticle
    :title="page.title"
    :description="page.description"
    :date="page.date"
    :tags="page.tags"
  >
    <ContentRenderer :value="page" />
  </CroutonContentArticle>
</template>

<script setup lang="ts">
const { data: page } = await useAsyncData('post', () =>
  queryContent('/blog/my-post').findOne()
)
</script>

Styling

All content components use Tailwind Typography (prose) classes for beautiful default styling. The prose styles are automatically applied with dark mode support.

Customizing Prose Styles

Override prose styles using Tailwind classes:

<template>
  <CroutonContentPage :content="content">
    <template #default>
      <div class="prose prose-lg prose-blue dark:prose-invert max-w-none">
        <div v-html="content" />
      </div>
    </template>
  </CroutonContentPage>
</template>

Available Prose Modifiers

  • prose-sm / prose-lg / prose-xl - Size variants
  • prose-blue / prose-green / etc. - Color variants for links
  • prose-invert - Dark mode (applied automatically)
  • max-w-none - Remove max-width constraint

Content Sources

These components are source-agnostic. Use them with any content:

SourceExample
Crouton Editor<CroutonEditorSimple v-model="content" />
Nuxt ContentqueryContent('/blog').findOne()
External CMSStrapi, Sanity, Contentful API
DatabaseDirect HTML from your DB
MarkdownRendered to HTML