---
title: "Slides: decks, slide layouts and exports"
description: "The add-on @skaile/forge-editor-slides: the Block types deck and slide, the pure HTML render contract, the render targets reveal-html, pptx and marp-md, the transfer transforms that turn blocks into slides, and the slide preview in the inspector."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/slides"
---
# Slides: decks, slide layouts and exports

> The add-on @skaile/forge-editor-slides: the Block types deck and slide, the pure HTML render contract, the render targets reveal-html, pptx and marp-md, the transfer transforms that turn blocks into slides, and the slide preview in the inspector.

The add-on `@skaile/forge-editor-slides` adds presentations to the editor. A **deck** is a Typed block of the Block type `deck`. Its body is **slides**, Typed blocks of the Block type `slide`. A slide has a title, a description (the subtitle of a title slide), a body, and three [Block properties](https://skaile-ai.github.io/forge-editor/guides/block-properties): `layout`, `notes` and `status`.

<table>
<thead>
  <tr>
    <th>
      Piece
    </th>
    
    <th>
      What it does
    </th>
    
    <th>
      Where
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        slideBlockTypes(locale)
      </code>
    </td>
    
    <td>
      the Block types <code>
        deck
      </code>
      
       (a container that accepts <code>
        slide
      </code>
      
      ) and <code>
        slide
      </code>
    </td>
    
    <td>
      editor and server
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        renderSlide
      </code>
      
      , <code>
        renderDeck
      </code>
    </td>
    
    <td>
      pure HTML and CSS of a slide or a deck
    </td>
    
    <td>
      anywhere
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slidesTargets()
      </code>
    </td>
    
    <td>
      the <a href="https://skaile-ai.github.io/forge-editor/guides/render">
        render targets
      </a>
      
       <code>
        reveal-html
      </code>
      
      , <code>
        pptx
      </code>
      
      , <code>
        marp-md
      </code>
    </td>
    
    <td>
      server
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slidesTransforms()
      </code>
    </td>
    
    <td>
      the transfer transforms <code>
        slides-from-blocks
      </code>
      
       and <code>
        slides-from-blocks-ai
      </code>
    </td>
    
    <td>
      editor
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slidesFeature()
      </code>
    </td>
    
    <td>
      the block actions „Folien-Layout“ and „Folie anhängen“, the slide preview in the inspector
    </td>
    
    <td>
      editor
    </td>
  </tr>
</tbody>
</table>

The add-on imports the library through its public entries only. The entry `@skaile/forge-editor-slides/server` reaches no editor code: use it in a server program.

## Install and wire

```ts
import { defaultBlockTypes } from '@skaile/forge-editor/composites'
import { copyTransform } from '@skaile/forge-editor/transfer'
import { bulletsTransform, summaryTransform } from '@skaile/forge-editor/integrations/composite-ai'
import { slideBlockTypes, slidesTransforms } from '@skaile/forge-editor-slides'
import { slidesFeature } from '@skaile/forge-editor-slides/vue' // or '/react'

const editor = composeEditor({
  content: 'rich',
  interaction: [...INTERACTION.full, 'transfer'],
  // The Block types of the editor: the built-in ones and the two of this add-on.
  blockTypes: [...defaultBlockTypes(t), ...slideBlockTypes('de')],
  add: [slidesFeature()],
  // A `transfer.transforms` list replaces the default list, so name the built-in transforms too.
  transfer: { transforms: [copyTransform, summaryTransform, bulletsTransform, ...slidesTransforms({ locale: 'de' })] },
})
```

```ts
import { createRenderer, markdownTarget } from '@skaile/forge-editor/adapters/render'
import { slideBlockTypes, slidesTargets } from '@skaile/forge-editor-slides/server'

// installEditorServer({ ports: { render }, blockTypes })
export const render = createRenderer([markdownTarget(), ...slidesTargets()])
export const blockTypes = [...serverBlockTypes, ...slideBlockTypes('de')]
```

The Block types go to the server too: the AI chat then knows what a deck is, and the server builds the schema from the same list ([Block types](https://skaile-ai.github.io/forge-editor/guides/block-types)).

## The slide model

<table>
<thead>
  <tr>
    <th>
      Part of a slide
    </th>
    
    <th>
      Source
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      title
    </td>
    
    <td>
      the title row of the Typed block (hidden on a <code>
        blank
      </code>
      
       slide)
    </td>
  </tr>
  
  <tr>
    <td>
      subtitle
    </td>
    
    <td>
      the description row (shown on a <code>
        title
      </code>
      
       slide)
    </td>
  </tr>
  
  <tr>
    <td>
      body
    </td>
    
    <td>
      the blocks after the chrome rows: paragraphs, headings, lists, quotes, code, images, rules
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        layout
      </code>
    </td>
    
    <td>
      Block property, a <code>
        select
      </code>
      
      : <code>
        title
      </code>
      
      , <code>
        bullets
      </code>
      
       (default), <code>
        two-column
      </code>
      
      , <code>
        image
      </code>
      
      , <code>
        blank
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        notes
      </code>
    </td>
    
    <td>
      Block property, a <code>
        textarea
      </code>
      
      : speaker notes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      Block property, a <code>
        status
      </code>
      
      : <code>
        draft
      </code>
      
       (todo), <code>
        ready
      </code>
      
       (doing), <code>
        final
      </code>
      
       (done)
    </td>
  </tr>
</tbody>
</table>

Layouts decide the arrangement only. `two-column` splits the body at the first horizontal rule, or after the first half of its blocks. `image` shows the first image above the other blocks. A deck ignores body blocks that are not slides.

## The render contract

`renderSlide(slide, layout?)` returns one slide in a 16:9 frame. `renderDeck(deck, { css? })` returns all slides, one frame each, in front of a `<style>` element with `SLIDE_CSS`. Both are pure: the same JSON gives the same string, and every text is escaped (links keep only `http`, `https`, `mailto` and relative targets; an image source must be web, relative or a `data:image/` address).

```ts
import { renderDeck } from '@skaile/forge-editor-slides/server'

const html = renderDeck(deckJson, { css: false })
// <div class="sk-deck" data-title="Quartalsbericht">
// <div class="sk-frame"><section class="sk-slide sk-slide--title" data-layout="title"><h1>Willkommen</h1>…
```

Nothing in the contract knows reveal.js, PowerPoint or Marp. Every target reads the same slide blocks (`blocksOf`) and builds its own format.

## Render targets

`slidesTargets(options)` gives three targets for a Block of the type `deck`. A render of any other block fails with a message the client shows.

<table>
<thead>
  <tr>
    <th>
      Target
    </th>
    
    <th>
      File
    </th>
    
    <th>
      What it holds
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        reveal-html
      </code>
    </td>
    
    <td>
      <code>
        .html
      </code>
    </td>
    
    <td>
      a reveal.js 6 page: one <code>
        <section>
      </code>
      
       per slide, the speaker notes as <code>
        <aside class="notes">
      </code>
      
       (speaker view with <code>
        S
      </code>
      
      ). reveal.js loads from <code>
        REVEAL_CDN
      </code>
      
       (jsDelivr); <code>
        options.reveal.cdn
      </code>
      
       points it at your own copy, <code>
        options.reveal.theme
      </code>
      
       picks the theme
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pptx
      </code>
    </td>
    
    <td>
      <code>
        .pptx
      </code>
    </td>
    
    <td>
      PowerPoint through PptxGenJS: the title as one text box, every block of the body as one text box, two stacks for <code>
        two-column
      </code>
      
      , pictures for <code>
        data:
      </code>
      
       images, speaker notes on the notes page
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        marp-md
      </code>
    </td>
    
    <td>
      <code>
        .md
      </code>
    </td>
    
    <td>
      Marp Markdown: front matter, <code>
        ---
      </code>
      
       between slides, the layout as a Marp class, speaker notes as HTML comments
    </td>
  </tr>
</tbody>
</table>

A `pptx` text box has a height from an estimate of the wrapped lines; PowerPoint shrinks text that does not fit when a person edits it. An image with a source other than a `data:` address shows its alt text, because the render cannot fetch it.

## Make slides from blocks

Drag a block into a deck and pick a transform, or drop it in the document and pick **Als Folien einfügen**. Both transforms insert a new `deck` Typed block after the drop position, titled „Folien: <source title="" />

“, with one slide per block of the source:

- A Typed block with nested Typed blocks gives one slide per nested block, depth first. Its own content comes first as a slide titled after it.
- Any other block is split at its headings: a heading starts a slide.
- A block without headings is one slide.

<table>
<thead>
  <tr>
    <th>
      Transform
    </th>
    
    <th>
      Derivation <code>
        method
      </code>
    </th>
    
    <th>
      Needs
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        slides-from-blocks
      </code>
    </td>
    
    <td>
      <code>
        link
      </code>
      
      : the structure is copied
    </td>
    
    <td>
      nothing
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slides-from-blocks-ai
      </code>
    </td>
    
    <td>
      <code>
        ai:slides
      </code>
      
      : a model writes the slides from the block's Markdown
    </td>
    
    <td>
      the <code>
        ai
      </code>
      
       capability
    </td>
  </tr>
</tbody>
</table>

The new deck records a [Derivation](https://skaile-ai.github.io/forge-editor/guides/derive-methods) that names the source block. `inputHash` is the hash of the source's body Markdown at that moment (the snapshot) and `outputHash` is the hash of the deck's body, so the Derivation badge shows the deck as stale when the source changes and as edited when the deck body changes. The AI transform also records `activity` (a request id and the model). A drag of a block that has no block id inserts the deck without a Derivation.

The AI transform asks for Markdown: every slide starts with `## Title`, bullets follow, optional lines `Layout: …` and `Notizen: …` set the layout and the speaker notes. The answer is parsed by `slideDraftsFromMarkdown`; an answer without a slide shows a notice and inserts nothing.

## In the editor

`slidesFeature()` of `/vue` or `/react` adds:

- the block action **Folien-Layout** on a slide (a menu of the layouts; `layouts` in the feature options narrows it) and **Folie anhängen** on a deck,
- the section **Folienvorschau** in the Typed block inspector and details card: a slide shows its 16:9 preview, layout buttons and notes; a deck shows all slides. The component is `SlideView`.

The framework-free `slidesFeature()` of the package entry has the block actions without the preview.

## Strings

The add-on keeps its own small catalog in German and English (`SLIDES_MESSAGES`, `createSlidesTranslator(locale, overrides)`), because the library's message keys are closed over the library's own strings. `slideBlockTypes(locale)` and `slidesTransforms({ locale })` take the language; the feature and the preview read it from the editor context.


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
