Components

Maizzle includes 30+ Vue components that produce email-client-compatible HTML.

They make it easy to create layouts that render well across email clients without needing to write complex tables or inline styles. You can also create your own components to encapsulate reusable patterns and custom logic.

Auto-import

All built-in components are automatically available in your templates, there is no need to manually import them. Also, any components that you add to paths defined in components.source are auto-imported as well.

Built-in components

The built-in components cover the most common HTML email design patterns in a modern workflow, from basic structures or content elements to special components that enable Tailwind support or render Markdown.

Structure

ComponentDescription
HtmlDocument root with lang, dir, and VML namespace attributes
HeadEmail <head> with essential meta tags
Body<body> element with resets
ContainerCentered content wrapper
SectionFull-width content block
RowContainer for columns
ColumnIndividual column within a Row
LayoutOpinionated email layout wrapper

Content

ComponentDescription
ButtonCTA button element
ImgResponsive image with dark mode support
SpacerVertical and horizontal spacing
HrHorizontal line separator
PreheaderHidden inbox preview text
HeadingHeadings with semantic markup
TextSemantic text wrapper
LinkUnstyled hyperlink

Special

ComponentDescription
TailwindEnables Tailwind CSS for styling
MarkdownRender markdown content
FontUse custom fonts
CodeBlockSyntax-highlighted code blocks
CodeInlineInline code formatting
OutlookShow content only in Outlook
NotOutlookHide content from Outlook
PlaintextShow content in plaintext only
NotPlaintextStrip content from plaintext output
QrCodeGenerate inline QR codes using tables
OutlookBgOutlook background images with VML
NoWidowsPrevent orphaned words
WithUrlAdd base URLs and tracking query params
RawEmit content verbatim, bypassing Vue parsing

Creating components

To define your own component, start by creating a .vue SFC in your project's components directory. For example, here's an Alert component that wraps content in a styled box:

components/Alert.vue
<script setup>
  // Component logic goes here
</script>

<template>
  <div class="p-4 border-l-4 border-yellow-500 bg-yellow-100 text-yellow-700">
    <slot />
  </div>
</template>

Save the file and use it in any template:

emails/welcome.vue
<template>
  <Layout>
    <Alert>
      Hurry while stocks last!
    </Alert>
  </Layout>
</template>

Props

Most built-in components accept props to customize their behavior and appearance.

Your editor should pick up props for both built-in and custom components, showing you type hints and autocompletion as you work. If not, make sure to update your editor setup, or simply restart its TS server/extension host.

If you're not familiar with Vue, props are custom attributes that you can pass to components to configure them. For example, let's add a type prop to the Alert component:

components/Alert.vue
<script setup>
  defineProps({
    type: {
      type: String,
      default: 'warning',
    },
  })
</script>

<template>
  <div :class="[
    'p-4 border-l-4',
    type === 'warning' ? 'border-yellow-500 bg-yellow-100 text-yellow-700' : '',
    type === 'error' ? 'border-red-500 bg-red-100 text-red-700' : '',
    type === 'info' ? 'border-blue-500 bg-blue-100 text-blue-700' : '',
  ]">
    <slot />
  </div>
</template>
emails/welcome.vue
<template>
  <Layout>
    <Alert type="error">
      Now you really screwed up.
    </Alert>
  </Layout>
</template>

Slots

Use slots to pass content into your components:

components/Card.vue
<template>
  <div class="bg-white rounded-lg p-6">
    <slot />  </div>
</template>

Named slots work too:

<template>
  <div class="bg-white rounded-lg p-6">
    <div class="mb-4">
      <slot name="header" />
    </div>
    <slot />
    <div class="mt-4">
      <slot name="footer" />
    </div>
  </div>
</template>
<template>
  <Card>
    <template #header>
      <Heading tag="h2">Special Offer</Heading>
    </template>
    <Text>Get 50% off your first order.</Text>
    <template #footer>
      <Button href="https://example.com">Shop Now</Button>
    </template>
  </Card>
</template>

Markdown

Components can also be authored as .md files. Drop one into your components directory and Maizzle auto-registers it under its PascalCased filename, just like a .vue component:

components/Promo.md
## Big Sale

Get **50% off** your first order this weekend only.

<Button href="https://example.com">Shop now</Button>
emails/welcome.vue
<template>
  <Layout>
    <Container class="max-w-xl">
      <Promo />    </Container>
  </Layout>
</template>

You can mix Vue components freely inside the markdown — <Button>, <Img>, your own components, anything auto-imported.

To accept props or use logic, add a <script setup> block at the top:

components/Promo.md
<script setup>
defineProps({
  discount: { type: Number, default: 50 },
})
</script>

## Big Sale

Get **{{ discount }}% off** your first order this weekend only.

<Button href="https://example.com">Shop now</Button>
emails/welcome.vue
<template>
  <Layout>
    <Container class="max-w-xl">
      <Promo :discount="25" />
    </Container>
  </Layout>
</template>

Slots work the same as in .vue components — drop a <slot /> anywhere in the markdown body to receive content from the parent template.

Locations

By default, Maizzle looks for custom components in the components directory at the root of your project. You may configure additional directories:

maizzle.config.ts
export default defineConfig({
  components: {
    source: ['components', 'src/shared/email-components'],
  },
})

Folder namespacing

Subfolder names automatically become a prefix on the component name. This means you can have multiple components with the same filename across different folders without conflicts:

components/
├── card/
│   └── Header.vue   ← <CardHeader />
└── alert/
    └── Header.vue   ← <AlertHeader />

Custom prefixes

For finer control, pass an object with a prefix instead of a string. Useful when you want a recognizable namespace that doesn't match the folder name:

maizzle.config.ts
export default defineConfig({
  components: {
    source: [
      { path: 'src/widgets', prefix: 'W' },
    ],
  },
})

Result: src/widgets/Button.vue is used as <WButton />.

Flattening nested folders

Set pathPrefix: false to skip intermediate folder names. The prefix is still applied, but subfolder structure is ignored — handy for icon sets or design-system primitives where folders are organizational only:

maizzle.config.ts
export default defineConfig({
  components: {
    source: [
      { path: 'src/icons', prefix: 'Icon', pathPrefix: false },
    ],
  },
})
File pathResolved name
src/icons/social/Twitter.vue<IconTwitter />
src/icons/ui/Chevron.vue<IconChevron />

Overriding built-in components

You can override any built-in component by creating a file with the same name in your components/ directory (or any directory listed in components.source). For example, components/Layout.vue in your project will override the built-in <Layout />.

components/
└── Layout.vue   ← replaces the built-in <Layout />
emails/
└── welcome.vue  ← will use your custom <Layout />