Skip to content

Layouts & Columns

Blockslides uses a flexible column-based layout system to structure content within slides. Columns are the primary layout containers that hold blocks, and they can be arranged side-by-side using column groups to create multi-column layouts.

Column vs ColumnGroup

Use Column for full-width content sections or as containers within column groups. Use ColumnGroup to place multiple columns horizontally side-by-side with precise width ratios.

Installation

Column and ColumnGroup extensions are included in the ExtensionKit by default:

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

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

If you're building a custom extension setup, you can import them individually:

ts
import { Column } from '@blockslides/extension-column'
import { ColumnGroup } from '@blockslides/extension-column-group'
import { BlockAttributes } from '@blockslides/extension-block-attributes'

const editor = useSlideEditor({
  extensions: [
    // BlockAttributes provides common attributes to columns
    BlockAttributes.configure({
      types: ['column', 'heading', 'paragraph', 'imageBlock']
    }),
    Column,
    ColumnGroup
  ]
})

Disabling columns

You can disable column extensions by setting them to false:

ts
ExtensionKit.configure({
  column: false,
  columnGroup: false
})

Columns

Columns are versatile container blocks that hold other blocks like paragraphs, headings, images, and lists. They provide the foundation for both simple full-width layouts and complex multi-column arrangements.

How columns work

Columns can be used in two ways:

  1. Standalone columns - Direct children of slides, spanning the full width
  2. Grouped columns - Children of a columnGroup, placed side-by-side horizontally
ts
// Full-width layout
// Will stack vertically
{
  type: 'slide',
  content: [
    { type: 'column', content: [...] },  // Full width
    { type: 'column', content: [...] }   // Full width
  ]
}

// Multi-column layout
// Will be side by side
{
  type: 'slide',
  content: [
    {
      type: 'columnGroup',
      content: [
        { type: 'column', content: [...] },  // Left column
        { type: 'column', content: [...] }   // Right column
      ]
    }
  ]
}

Column attributes

Columns inherit attributes from the BlockAttributes extension, providing extensive styling and layout control:

Alignment

Control horizontal alignment and self-alignment within flex containers:

ts
// Align left
editor.commands.setBlockAlign('left')

// Center align (default for most content)
editor.commands.setBlockAlign('center')

// Align right
editor.commands.setBlockAlign('right')

// Stretch to fill width
editor.commands.setBlockAlign('stretch')

Values:

  • left - Aligns content to the left edge
  • center - Centers content horizontally
  • right - Aligns content to the right edge
  • stretch - Stretches to fill available width

Vertical distribution (justify)

Control how child blocks are distributed vertically within a column:

ts
// Align children to top
editor.commands.updateAttributes('column', {
  justify: 'start'
})

// Center children vertically
editor.commands.updateAttributes('column', {
  justify: 'center'
})

// Align children to bottom
editor.commands.updateAttributes('column', {
  justify: 'end'
})

// Distribute with space between
editor.commands.updateAttributes('column', {
  justify: 'space-between'
})

Values:

  • start - Children align to the top
  • center - Children center vertically
  • end - Children align to the bottom
  • space-between - Maximum space between children, first and last children touch edges

Spacing

Control internal spacing (padding), external spacing (margin), and spacing between child blocks (gap):

ts
// Set padding using semantic tokens
editor.commands.setBlockPadding('lg')

// Set padding using custom CSS values
editor.commands.setBlockPadding('2rem')

// Set gap between child blocks
editor.commands.setBlockGap('md')

// Set external margin
editor.commands.setBlockMargin('sm')

Spacing tokens:

  • none - 0
  • sm - 8px
  • md - 16px
  • lg - 32px

You can also use any CSS value like "1.5rem", "24px", or "10%".

ts
// Example: Column with padding and gap for card-like appearance
editor.commands.updateAttributes('column', {
  padding: 'lg',
  gap: 'md',
  borderRadius: 'md',
  backgroundColor: '#f8f9fa'
})

Background styling

Add background colors and images to columns:

ts
// Solid background color
editor.commands.setBlockBackgroundColor('#3b82f6')

// Background image
editor.commands.setBlockBackgroundImage('https://example.com/bg.jpg')

// Remove background
editor.commands.setBlockBackgroundColor(null)
ts
// Example: Hero column with background image
editor.commands.updateAttributes('column', {
  backgroundImage: 'https://example.com/hero-bg.jpg',
  padding: 'lg',
  justify: 'center',
  align: 'center'
})

Border styling

Control corner rounding and borders:

ts
// Border radius using tokens
editor.commands.setBlockBorderRadius('md')

// Custom border radius
editor.commands.setBlockBorderRadius('20px')

// Custom border
editor.commands.updateAttributes('column', {
  border: '2px solid #e5e7eb'
})

Border radius tokens:

  • none - 0
  • sm - 4px
  • md - 8px
  • lg - 16px

Sizing

Control column dimensions and flex behavior:

ts
// Make column fill available vertical space
editor.commands.setBlockFill(true)

// Set explicit width
editor.commands.setBlockWidth('400px')

// Set explicit height
editor.commands.setBlockHeight('500px')

// Remove dimension constraints
editor.commands.setBlockWidth(null)
editor.commands.setBlockHeight(null)

The fill attribute makes columns expand to fill available space using flex: 1 1 auto. This is useful for creating columns that take up remaining vertical space in a slide.

ts
// Example: Fixed-width sidebar column
editor.commands.updateAttributes('column', {
  width: '250px',
  fill: false
})

Configuration options

Customize the Column extension:

ts
Column.configure({
  // Add custom HTML attributes
  HTMLAttributes: {
    class: 'custom-column'
  },
  
  // Control CSS injection
  injectCSS: true,
  
  // Add CSP nonce for injected styles
  injectNonce: 'your-nonce-value'
})

Column Groups

Column groups arrange multiple columns horizontally side-by-side with precise control over their relative widths. They support predefined layout ratios and offer additional styling options like backgrounds and overlays.

Layout ratios

Column groups use layout strings to define the relative width of each column. The numbers represent flex ratios that determine how horizontal space is distributed.

Predefined layouts

Single column:

  • "1" - One column (equivalent to a standalone column)

Two columns:

  • "1-1" - Equal width columns (50% / 50%)
  • "2-1" - Left column twice as wide (66.67% / 33.33%)
  • "1-2" - Right column twice as wide (33.33% / 66.67%)

Three columns:

  • "1-1-1" - Three equal columns (33.33% each)
  • "2-1-1" - Large left, two smaller (50% / 25% / 25%)
  • "1-2-1" - Large center (25% / 50% / 25%)
  • "1-1-2" - Large right (25% / 25% / 50%)

Four columns:

  • "1-1-1-1" - Four equal columns (25% each)
ts
// Set layout on a columnGroup
editor.commands.updateAttributes('columnGroup', {
  layout: '2-1'
})

How layout ratios work

Layout ratios use flex values to distribute space proportionally:

  • "1-1" → Each column gets flex: 1, splitting space equally
  • "2-1" → First column gets flex: 2, second gets flex: 1 (2:1 ratio)
  • "1-2-1" → Ratios of 1:2:1 distribute space as 25% / 50% / 25%

The total ratio (sum of numbers) determines each column's percentage:

  • "2-1" = 3 total → 2/3 and 1/3
  • "3-1" = 4 total → 3/4 and 1/4
  • "5-3-2" = 10 total → 50%, 30%, 20%

Setting layouts

Update the layout attribute when your cursor is in a columnGroup:

ts
// Two equal columns
editor.commands.updateAttributes('columnGroup', {
  layout: '1-1'
})

// 2:1 ratio for content and sidebar
editor.commands.updateAttributes('columnGroup', {
  layout: '2-1'
})

// Three equal columns for features
editor.commands.updateAttributes('columnGroup', {
  layout: '1-1-1'
})

Background options

Column groups support three background modes with additional overlay capabilities:

ts
// Solid color background
editor.commands.updateAttributes('columnGroup', {
  backgroundMode: 'color',
  backgroundColor: '#1e293b'
})

// Background image
editor.commands.updateAttributes('columnGroup', {
  backgroundMode: 'image',
  backgroundImage: 'https://example.com/pattern.jpg'
})

// Background image with color overlay
editor.commands.updateAttributes('columnGroup', {
  backgroundMode: 'imageOverlay',
  backgroundImage: 'https://example.com/photo.jpg',
  backgroundOverlayColor: '#000000',
  backgroundOverlayOpacity: 0.5
})

// No background
editor.commands.updateAttributes('columnGroup', {
  backgroundMode: 'none'
})

Background modes:

  • none - No background (default)
  • color - Solid color background
  • image - Background image (cover, centered, no-repeat)
  • imageOverlay - Background image with semi-transparent color overlay

The overlay is useful for ensuring text readability over images:

ts
// Example: Hero section with readable text over image
editor.commands.updateAttributes('columnGroup', {
  backgroundMode: 'imageOverlay',
  backgroundImage: 'https://example.com/hero.jpg',
  backgroundOverlayColor: '#000000',
  backgroundOverlayOpacity: 0.4  // 40% black overlay
})

Fill behavior

Control whether a column group expands to fill available vertical space:

ts
// Fill remaining vertical space
editor.commands.updateAttributes('columnGroup', {
  fill: true
})

// Use natural height
editor.commands.updateAttributes('columnGroup', {
  fill: false
})

When fill: true, the column group uses flex: 1 1 auto to expand and take up remaining space in the slide. This is useful for creating full-height sections.

Configuration options

Customize the ColumnGroup extension:

ts
ColumnGroup.configure({
  // Add custom HTML attributes
  HTMLAttributes: {
    class: 'custom-column-group'
  },
  
  // Control base CSS injection
  injectCSS: true,
  
  // Control layout-specific CSS (data-layout selectors)
  enableLayoutCSS: true,
  
  // Add CSP nonce for injected styles
  injectNonce: 'your-nonce-value'
})

Working with Layouts Programmatically

Querying columns and column groups

Find and inspect layout elements in your document:

ts
// Find all columns
const columns: any[] = []
editor.state.doc.descendants((node) => {
  if (node.type.name === 'column') {
    columns.push({
      padding: node.attrs.padding,
      align: node.attrs.align,
      backgroundColor: node.attrs.backgroundColor
    })
  }
})

console.log('Found columns:', columns)
ts
// Find all column groups with their layouts
const columnGroups: any[] = []
editor.state.doc.descendants((node) => {
  if (node.type.name === 'columnGroup') {
    columnGroups.push({
      layout: node.attrs.layout,
      fill: node.attrs.fill,
      backgroundMode: node.attrs.backgroundMode
    })
  }
})

console.log('Found column groups:', columnGroups)

Getting layout at cursor position

Check if the current selection is within a column or column group:

ts
const { from } = editor.state.selection
let pos = from

// Walk up the tree to find parent column/columnGroup
editor.state.doc.nodesBetween(from, from, (node, nodePos) => {
  if (node.type.name === 'column') {
    console.log('Inside column at position:', nodePos)
    console.log('Column attributes:', node.attrs)
  }
  
  if (node.type.name === 'columnGroup') {
    console.log('Inside column group with layout:', node.attrs.layout)
  }
})

Updating layouts dynamically

Programmatically update column and column group attributes:

ts
// Find and update all columnGroups to use 2-1 layout
editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'columnGroup') {
    // Select the columnGroup node
    editor.commands.setTextSelection(pos)
    
    // Update its layout
    editor.commands.updateAttributes('columnGroup', {
      layout: '2-1'
    })
  }
})
ts
// Update all columns to have consistent padding
editor.state.doc.descendants((node, pos) => {
  if (node.type.name === 'column') {
    editor.commands.setTextSelection(pos)
    editor.commands.setBlockPadding('md')
  }
})