Skip to content

Creating & Organizing ​

Blockslides provides tools for creating and managing slides programmatically. You can add slides through the AddSlideButton extension for UI-based insertion, or use editor commands for programmatic control.

Installation ​

The AddSlideButton extension is included in the ExtensionKit by default:

ts
import { ExtensionKit } from '@blockslides/extension-kit'

const editor = useSlideEditor({
  extensions: [
    ExtensionKit.configure({})
  ]
})

To configure the AddSlideButton:

ts
ExtensionKit.configure({
  addSlideButton: {
    content: '+',
    showPresets: true,
    presets: myPresetTemplates
  }
})

Disabling AddSlideButton ​

You can disable the AddSlideButton extension by setting it to false:

ts
ExtensionKit.configure({
  addSlideButton: false
})

Adding Slides ​

AddSlideButton Extension ​

The AddSlideButton extension renders interactive buttons between slides, allowing users to add new slides at any position in the deck. This is the recommended approach for UI-based slide creation.

Basic configuration ​

ts
import { AddSlideButton } from '@blockslides/extension-add-slide-button'

AddSlideButton.configure({
  content: '+',
  buttonStyle: {
    backgroundColor: '#3b82f6',
    color: '#ffffff'
  }
})

Configuration options ​

content (string, default: '+')

The button content. Accepts text, HTML, emojis, or SVG icons:

ts
AddSlideButton.configure({
  content: '➕'  // Emoji
})

AddSlideButton.configure({
  content: 'Add Slide'  // Text
})

AddSlideButton.configure({
  content: '<svg>...</svg>'  // Custom icon
})

buttonStyle (Record<string, string>, default: {})

Custom CSS styles for the button. Supports both camelCase (React) and kebab-case (CSS) property names:

ts
AddSlideButton.configure({
  buttonStyle: {
    backgroundColor: '#000000',
    color: '#ffffff',
    borderRadius: '12px',
    padding: '12px 24px'
  }
})

onClick (function | null, default: null)

Custom click handler for the add button. When provided, replaces the default slide insertion behavior:

ts
AddSlideButton.configure({
  onClick: ({ slideIndex, position, view, event }) => {
    // Custom logic here
    // slideIndex: the current slide index (0-based)
    // position: document position where the slide would be inserted
    // view: EditorView instance
    // event: the MouseEvent
  }
})

Template Picker ​

showPresets (boolean, default: false)

Template Picker Feature

Enable the preset template picker to give users a selection of pre-designed slide templates. When enabled, a template picker button appears alongside the regular add slide button, opening a modal with template options.

Using Built-in Presets:

Import and use the built-in template presets from @blockslides/ai-context:

ts
import { templatesV1 } from '@blockslides/ai-context'

AddSlideButton.configure({
  showPresets: true,
  presets: templatesV1.listPresetTemplates()
})

The built-in presets include templates like Title & Subheader, Two Columns, Three Columns, Image & Text, Full Image, and more.

presets (PresetTemplateOption[])

You can also define your own custom template definitions:

ts
AddSlideButton.configure({
  showPresets: true,
  presets: [
    {
      key: 'title-slide',
      label: 'Title Slide',
      icon: '<svg>...</svg>',
      build: () => ({
        type: 'slide',
        content: [/* slide content */]
      })
    },
    {
      key: 'two-column',
      label: 'Two Column Layout',
      icon: '<svg>...</svg>',
      build: () => ({
        type: 'slide',
        content: [/* slide content */]
      })
    }
  ]
})

Deleting Slides ​

Use deleteNode() to remove the current slide:

ts
editor.commands.deleteNode('slide')

This removes the slide node that contains the current cursor position.

Deleting specific slides ​

To delete a specific slide, first select it, then delete:

ts
// Find and delete a slide by position
editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'slide') {
    // Check if this is the slide you want to delete
    if (/* your condition */) {
      editor.commands.setNodeSelection(pos)
      editor.commands.deleteNode('slide')
    }
  }
})

Slide Attributes ​

Every slide has attributes that control its appearance and behavior:

size ​

Determines the slide dimensions. We ship with a few options (and are adding more)
Available presets:

  • 16x9 - Widescreen format (1920×1080px equivalent ratio)
  • 4x3 - Standard format (1600×1200px equivalent ratio)
  • a4-portrait - A4 paper in portrait orientation
  • a4-landscape - A4 paper in landscape orientation
  • letter-portrait - US Letter in portrait orientation
  • letter-landscape - US Letter in landscape orientation
  • linkedin-banner - LinkedIn banner format (1584×396px)
ts
editor.commands.insertContent({
  type: 'slide',
  attrs: {
    size: '16x9'
  },
  content: [...]
})

backgroundMode ​

Controls how the background is rendered:

  • none - No background (uses default)
  • color - Solid color background
  • image - Image background
  • imageOverlay - Image with a colored overlay
ts
// Color mode
{
  backgroundMode: 'color',
  backgroundColor: '#3b82f6'
}

// Image mode
{
  backgroundMode: 'image',
  backgroundImage: 'https://example.com/bg.jpg'
}

// Image with overlay
{
  backgroundMode: 'imageOverlay',
  backgroundImage: 'https://example.com/bg.jpg',
  backgroundOverlayColor: '#000000',
  backgroundOverlayOpacity: 0.6
}

backgroundColor ​

The background color when backgroundMode is 'color':

ts
{
  backgroundMode: 'color',
  backgroundColor: '#f3f4f6'
}

backgroundImage ​

The background image URL when backgroundMode is 'image' or 'imageOverlay':

ts
{
  backgroundMode: 'image',
  backgroundImage: 'https://example.com/slide-bg.jpg'
}

backgroundOverlayColor ​

The overlay color when backgroundMode is 'imageOverlay':

ts
{
  backgroundMode: 'imageOverlay',
  backgroundImage: 'https://example.com/bg.jpg',
  backgroundOverlayColor: '#1e293b',
  backgroundOverlayOpacity: 0.7
}

backgroundOverlayOpacity ​

The overlay opacity (0 to 1) when backgroundMode is 'imageOverlay':

ts
{
  backgroundMode: 'imageOverlay',
  backgroundImage: 'https://example.com/bg.jpg',
  backgroundOverlayColor: '#000000',
  backgroundOverlayOpacity: 0.5  // 50% opacity
}

id ​

Optional identifier for the slide:

ts
{
  id: 'intro-slide',
  // ...other attributes
}

className ​

Optional CSS class names for custom styling:

ts
{
  className: 'highlight-slide branded-slide',
  // ...other attributes
}

Use Attributes Instead of ClassNames

Avoid relying on className for slide styling. Instead, use the built-in slide attributes like backgroundColor, backgroundMode, backgroundImage, etc. for better semantic structure and maintainability.

Querying Slides ​

Counting slides ​

Count the total number of slides in the document:

ts
let slideCount = 0
editor.state.doc.descendants((node) => {
  if (node.type.name === 'slide') {
    slideCount++
  }
})

console.log(`Total slides: ${slideCount}`)

Finding all slides ​

Collect all slides with their positions:

ts
const slides: Array<{ node: any; pos: number }> = []

editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'slide') {
    slides.push({ node, pos })
  }
})

console.log('All slides:', slides)

Finding a slide by index ​

Locate a specific slide by its index (0-based):

ts
const targetIndex = 2  // Find the third slide
let currentIndex = 0
let targetSlide = null
let targetPos = 0

editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'slide') {
    if (currentIndex === targetIndex) {
      targetSlide = node
      targetPos = pos
      return false  // Stop traversal
    }
    currentIndex++
  }
})

if (targetSlide) {
  console.log('Found slide at position:', targetPos)
}

Finding a slide by ID ​

Locate a slide with a specific ID attribute:

ts
const targetId = 'intro-slide'
let foundSlide = null
let foundPos = 0

editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'slide' && node.attrs.id === targetId) {
    foundSlide = node
    foundPos = pos
    return false  // Stop traversal
  }
})

if (foundSlide) {
  console.log('Found slide with ID:', targetId)
}

Getting the current slide ​

Find the slide containing the cursor:

ts
const cursorPos = editor.state.selection.from
let currentSlide = null
let currentSlidePos = 0

editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'slide') {
    const slideEnd = pos + node.nodeSize
    if (cursorPos >= pos && cursorPos <= slideEnd) {
      currentSlide = node
      currentSlidePos = pos
      return false  // Stop traversal
    }
  }
})

if (currentSlide) {
  console.log('Current slide:', currentSlide.attrs)
}

Accessing slide attributes ​

Read slide attributes for inspection or modification:

ts
editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'slide') {
    console.log('Slide attributes:', {
      size: node.attrs.size,
      backgroundMode: node.attrs.backgroundMode,
      backgroundColor: node.attrs.backgroundColor,
      backgroundImage: node.attrs.backgroundImage,
      id: node.attrs.id,
      className: node.attrs.className
    })
  }
})

Working with Slide Positions ​

Understanding document positions is essential for programmatic slide manipulation.

Document positions ​

In ProseMirror (the editor framework Blockslides uses), positions are numeric offsets from the start of the document. Each character, node boundary, and structural element occupies a position.

ts
// Position 0: Start of document
// Position N: After N characters/nodes
// Position doc.content.size: End of document

editor.state.doc.descendants((node, pos) => {
  console.log(`Node type: ${node.type.name}, starts at position: ${pos}`)
})

Inserting at specific positions ​

Use positions to insert content at precise locations:

ts
// Insert at the beginning
editor.commands.insertContentAt(0, slideJSON)

// Insert at the end
const endPos = editor.state.doc.content.size
editor.commands.insertContentAt(endPos, slideJSON)

// Insert after a specific slide
const insertPos = slidePos + slideNode.nodeSize
editor.commands.insertContentAt(insertPos, slideJSON)

Selecting slides by position ​

Set the selection to a specific slide:

ts
editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'slide') {
    // Select this slide
    editor.commands.setNodeSelection(pos)
    return false  // Stop after first slide
  }
})

Key considerations:

  • Positions are absolute offsets from document start
  • Each node takes up nodeSize positions (includes opening/closing boundaries)
  • Positions change as content is inserted or deleted
  • Use descendants() to traverse and find positions