Fractional Indexing

How CF2 uses fractional indexing to maintain element order within containers, enabling insertions between elements without reindexing.

Overview

Every element in a CF2 page tree has a fractionalIndex property that determines its position among siblings. This system allows inserting elements between any two existing elements without having to renumber all subsequent elements.

i
Library: CF2 fractional indexing is based on rocicorp/fractional-indexing

Basic Structure

Each element includes a fractionalIndex in its base properties:

JSON
{
  "type": "Headline/V1",
  "id": "6Z-HdLne-0",
  "version": 0,
  "parentId": "6Z-FlxCn-0",
  "fractionalIndex": "a0"
}

Ordering Sequence

Fractional indices follow a lexicographic ordering pattern:

Sequence
a0 → a1 → a2 → ... → a9 → aA → aB → ... → aZ → b0 → b1 → ...
Position Index Notes
First element a0 Standard starting point
Second element a1 Sequential increment
After a9 aA Continues with uppercase letters
After aZ b0 Increments first character
Between a0 and a1 a0V Adds character to create midpoint

Using the Library

Install the fractional-indexing library:

Bash
npm install fractional-indexing

Key Functions

Function Description
generateKeyBetween(a, b) Generate a key between two existing keys
generateNKeysBetween(a, b, n) Generate n keys between two existing keys

Insertion Examples

Insert Between Elements

JavaScript
import { generateKeyBetween } from 'fractional-indexing';

// Insert between 'a0' and 'a1'
const newKey = generateKeyBetween('a0', 'a1');
// Returns 'a0V'

Insert at Beginning

JavaScript
import { generateKeyBetween } from 'fractional-indexing';

// Insert before 'a0' (at the beginning)
const firstKey = generateKeyBetween(null, 'a0');
// Returns 'Zz'

Insert at End

JavaScript
import { generateKeyBetween } from 'fractional-indexing';

// Insert after 'a9' (at the end)
const lastKey = generateKeyBetween('a9', null);
// Returns 'aA'

Insert Multiple Elements

JavaScript
import { generateNKeysBetween } from 'fractional-indexing';

// Insert 3 elements between 'a0' and 'a1'
const keys = generateNKeysBetween('a0', 'a1', 3);
// Returns ['a0G', 'a0V', 'a0l']

Practical Example

A container with three children in order:

JSON
{
  "type": "FlexContainer/V1",
  "id": "6Z-FlxCn-0",
  "children": [
    {
      "type": "Headline/V1",
      "id": "6Z-HdLn1-0",
      "parentId": "6Z-FlxCn-0",
      "fractionalIndex": "a0"
    },
    {
      "type": "Paragraph/V1",
      "id": "6Z-Prgph-0",
      "parentId": "6Z-FlxCn-0",
      "fractionalIndex": "a1"
    },
    {
      "type": "Button/V1",
      "id": "6Z-Buttn-0",
      "parentId": "6Z-FlxCn-0",
      "fractionalIndex": "a2"
    }
  ]
}

To insert an Image between the Paragraph (a1) and Button (a2):

JavaScript
import { generateKeyBetween } from 'fractional-indexing';

const imageIndex = generateKeyBetween('a1', 'a2'); // 'a1V'

const newImage = {
  type: "Image/V2",
  id: "6Z-Image-0",
  parentId: "6Z-FlxCn-0",
  fractionalIndex: imageIndex  // 'a1V'
};

The resulting order would be:

Order
Headline  → a0
Paragraph → a1
Image     → a1V  (new element)
Button    → a2

Sorting Elements

When rendering, sort children by their fractionalIndex:

JavaScript
// Sort children by fractionalIndex
const sortedChildren = children.sort((a, b) =>
  a.fractionalIndex.localeCompare(b.fractionalIndex)
);
i
Tip: Fractional indices are designed to be compared lexicographically using standard string comparison.

Benefits

  • No reindexing: Insert elements without updating sibling indices
  • Conflict-free: Multiple users can insert elements simultaneously without conflicts
  • Unlimited insertions: Can always generate a key between any two existing keys
  • Simple comparison: Standard string comparison determines order