Skip to content

Instantly share code, notes, and snippets.

@pfrazee
Last active March 7, 2026 08:41
Show Gist options
  • Select an option

  • Save pfrazee/5b845f9703e5ea553b99706be4112e21 to your computer and use it in GitHub Desktop.

Select an option

Save pfrazee/5b845f9703e5ea553b99706be4112e21 to your computer and use it in GitHub Desktop.

Rich-text Array-formay Blocks and Spans

These documents are composed of blocks (structural content elements) which contain spans (text segments with rich formatting).

Examples

Example 1: A paragraph with mixed formatting

"Hello world, this is bold and this is a link."

{
  "$type": "com.example.block#text",
  "spans": [
    { "text": "Hello world, this is " },
    { "text": "bold", "bold": true },
    { "text": " and this is a " },
    { "text": "link", "features": [{ "$type": "com.example.span#link", "uri": "https://example.com" }] },
    { "text": "." }
  ]
}

Example 2: A header followed by a blockquote with italic + bold

Introduction

"To be or not to be, that is the question."

[
  {
    "$type": "com.example.block#header",
    "level": 2,
    "spans": [
      { "text": "Introduction" }
    ]
  },
  {
    "$type": "com.example.block#blockquote",
    "spans": [
      { "text": "To be or " },
      { "text": "not to be", "bold": true, "italic": true },
      { "text": ", that is the question." }
    ]
  }
]

Example 3: A list with inline code and a mention

  • Use getRecord() to fetch data
  • See @alice's guide for details
{
  "$type": "com.example.block#list",
  "children": [
    {
      "content": {
        "$type": "com.example.block#text",
        "spans": [
          { "text": "Use " },
          { "text": "getRecord()", "code": true },
          { "text": " to fetch data" }
        ]
      }
    },
    {
      "content": {
        "$type": "com.example.block#text",
        "spans": [
          { "text": "See " },
          { "text": "@alice", "features": [{ "$type": "com.example.span#mention", "did": "did:plc:alice123" }] },
          { "text": "'s guide for details" }
        ]
      }
    }
  ]
}

Spans (com.example.span)

A span is a segment of rich text with its own text content and optional features. Blocks that contain rich text use an array of spans — each span carries its text and the features that apply to the entire span. No byte-slice indexing is needed; formatting boundaries are expressed by splitting text into separate spans.

Field Type Required Description
text string yes The text content of this span
bold boolean no Bold text
italic boolean no Italic text
underline boolean no Underlined text
strike boolean no Strikethrough text
code boolean no Inline code
highlight boolean no Highlighted text
features array of feature union (#link, #mention, #bold, #italic, #underline, #strikethrough, #code, #highlight) no Additional features applied to this span's text

Features (com.example.span#*)

Features are rich text annotations that can be applied to a span.

Feature ID Properties Description
Bold #bold (none) Bold text
Italic #italic (none) Italic text
Underline #underline (none) Underlined text
Strikethrough #strikethrough (none) Strikethrough text
Code #code (none) Inline code
Highlight #highlight (none) Highlighted text
Link #link uri (string, required) Hyperlink. The display text may be simplified but the facet URI should be the complete URL.
Mention #mention did (string, format: did, required) Mention of a DID identity

Blocks (com.example.block#*)

#text

A paragraph of rich text.

Field Type Required Description
spans array of com.example.span yes The rich text content
textSize string enum: "default", "small", "large" no Text size variant

#header

A heading element (h1-h6).

Field Type Required Description
spans array of com.example.span yes The heading text
level integer (1-6) no Heading level
id string, optional Identifier string, used for linking to a specific heading

#blockquote

A block quotation with rich text.

Field Type Required Description
spans array of com.example.span yes The quoted text

#image

An embedded image with required aspect ratio.

Field Type Required Description
image blob (accept: image/*, max 1MB) yes The image data
aspectRatio object: { width: integer, height: integer } yes Width and height for layout
alt string no Alt text for accessibility

#code

A code block with optional syntax highlighting.

Field Type Required Description
code string yes The code content
language string no Programming language identifier
syntaxHighlightingTheme string no Theme for syntax highlighting

#list

A list. Each list item contains a content block which can be a nested list.

Field Type Required Description
children array of union: #text | #header | #image | #list yes The list items
style string numbers or bullets no What kind of list

#button

A clickable button/link.

Field Type Required Description
text string yes Button label
url string (format: uri) yes Destination URL

#website

A website embed/link card with optional preview.

Field Type Required Description
src string (format: uri) yes The website URL
title string no Page title
description string no Page description
previewImage blob (accept: image/*, max 1MB) no Preview/OG image

#object

An embedded atproto object.

Field Type Required Description
ref com.atproto.repo.strongRef yes Reference to the record (URI + CID)

#actor

An embedded atproto actor.

Field Type Required Description
did string yes Reference to the user (DID)

#iframe

An embedded iframe.

Field Type Required Description
url string (format: uri) yes The iframe source URL
height integer (16-1600) no Height in pixels

#math

A LaTeX math block.

Field Type Required Description
tex string yes LaTeX source

#hr

A horizontal divider/separator. Has no properties.

@pfrazee
Copy link
Author

pfrazee commented Feb 20, 2026

Bonus premise: a "Fallbacker" block that handles multiple supported formats

[
  {
    "$type": "com.example.block#header",
    "level": 2,
    "spans": [
      { "text": "Check out this post" }
    ]
  },
  {
    "$type": "com.example.block#fallbacker",
    "blocks": [
      {"$type": "pub.leaflet.blocks.bskyPost", ...},
      {"$type": "com.example.block#text", "spans": [{"text": "Polls not supported, sorry :("}]}
    ]
  }
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment