Eleventy Adapter

The Eleventy adapter (@refrakt-md/eleventy) integrates refrakt.md with Eleventy (11ty) v3. It is the simplest adapter — no Vite, no bundler, just Eleventy's template engine and data cascade. Content is loaded at build time, transformed to HTML strings, and injected into Nunjucks (or Liquid) templates.

Installation

npm install @refrakt-md/eleventy @refrakt-md/content @refrakt-md/runes @refrakt-md/transform @refrakt-md/types @refrakt-md/lumina @markdoc/markdoc

Configuration

Set target to "eleventy" in refrakt.config.json:

{
  "contentDir": "./content",
  "theme": "@refrakt-md/lumina",
  "target": "eleventy",
  "routeRules": [
    { "pattern": "**", "layout": "default" }
  ]
}

EleventyTheme Interface

Like the HTML adapter, the Eleventy adapter uses a theme interface with no component registry — all runes render through the identity transform and renderToHtml():

interface EleventyTheme {
  manifest: ThemeManifest;
  layouts: Record<string, LayoutConfig>;
}

Interactive runes get their behavior from @refrakt-md/behaviors via client-side initialization in the template.

Project Structure

A typical Eleventy + refrakt project looks like this:

my-site/
├── content/                # Markdoc content (separate from Eleventy templates)
   ├── index.md
   └── docs/
       └── getting-started.md
├── _data/
   └── refrakt.js          # Global data file — loads and transforms content
├── _includes/
   └── base.njk            # Base Nunjucks template
├── pages.njk               # Pagination template — one page per content item
├── eleventy.config.js      # Eleventy configuration
├── refrakt.config.json     # refrakt configuration
└── package.json
note

Keep the content directory separate from Eleventy's template input directory. Eleventy should not try to process .md files in content/ as its own Markdown — refrakt handles all Markdown processing through Markdoc.

Plugin Setup

Register the refrakt plugin in your Eleventy configuration file. The plugin configures passthrough file copy for theme CSS:

import { refraktPlugin } from '@refrakt-md/eleventy';

export default function (eleventyConfig) {
  eleventyConfig.addPlugin(refraktPlugin, {
    cssFiles: ['node_modules/@refrakt-md/lumina/index.css'],
    cssPrefix: '/css',
  });

  // Ignore the content directory — refrakt processes it, not Eleventy
  eleventyConfig.ignores.add('content/**');

  return {
    dir: {
      input: '.',
      includes: '_includes',
      data: '_data',
      output: '_site',
    },
  };
}

RefraktEleventyOptions

OptionTypeDefaultDescription
configPathstring'./refrakt.config.json'Path to the refrakt config file
cssFilesstring[]CSS file paths to passthrough copy (typically from node_modules)
cssPrefixstring'/css'URL prefix for copied CSS files in the output

Global Data File

The createDataFile function produces an Eleventy global data file that loads all refrakt content, applies the identity and layout transforms, and returns an array of page objects with pre-rendered HTML.

Create _data/refrakt.js:

import { createDataFile } from '@refrakt-md/eleventy';
import manifest from '@refrakt-md/lumina/manifest';
import { layouts } from '@refrakt-md/lumina/layouts';
const theme = { manifest, layouts };

export default createDataFile({
  theme,
  contentDir: './content',
  basePath: '/',
});

createDataFile Options

OptionTypeDefaultDescription
themeEleventyThemeTheme definition (required)
contentDirstring'./content'Path to the content directory
basePathstring'/'Base URL path for all generated pages
configPathstring'./refrakt.config.json'Path to refrakt config

EleventyPageData

Each item in the returned array has this shape:

interface EleventyPageData {
  url: string;           // e.g. '/docs/getting-started/'
  title: string;         // Page title from frontmatter
  html: string;          // Pre-rendered HTML (identity + layout transform)
  seo: {
    title: string;       // Resolved page title
    description: string; // Meta description
    metaTags: string;    // Pre-built <meta> tags (OG, Twitter)
    jsonLd: string;      // Pre-built <script type="application/ld+json"> tags
  };
  frontmatter: Record<string, unknown>;
}

Base Template

Use a Nunjucks template that outputs the pre-rendered HTML with the | safe filter (to prevent HTML escaping):

{# _includes/base.njk #}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  {% if page.seo.title %}<title>{{ page.seo.title }}</title>{% endif %}
  {{ page.seo.metaTags | safe }}
  {{ page.seo.jsonLd | safe }}
  <link rel="stylesheet" href="/css/index.css">
</head>
<body>
  {{ page.html | safe }}
  <script type="module">
    import { registerElements, RfContext, initRuneBehaviors, initLayoutBehaviors } from '/js/behaviors.js';

    RfContext.currentUrl = '{{ page.url }}';
    registerElements();
    initRuneBehaviors();
    initLayoutBehaviors();
  </script>
</body>
</html>
warning

Always use | safe when outputting page.html, page.seo.metaTags, and page.seo.jsonLd. Without it, Nunjucks escapes the HTML entities and the output renders as visible markup instead of formatted content.

A reference template is included in the package at @refrakt-md/eleventy/templates/base.njk.

Pagination

Use Eleventy's pagination to generate one HTML page per content item from the global data:

---js
{
  pagination: {
    data: "refrakt",
    size: 1,
    alias: "page"
  },
  permalink: "{{ page.url }}",
  layout: "base.njk"
}
---

Save this as pages.njk at the root of your input directory. Eleventy iterates over the refrakt data array and generates a page for each item, using the url from the content as the output permalink.

CSS Setup

Theme CSS needs to be copied into the output directory. The plugin's cssFiles option handles this via Eleventy's passthrough file copy:

eleventyConfig.addPlugin(refraktPlugin, {
  cssFiles: ['node_modules/@refrakt-md/lumina/index.css'],
  cssPrefix: '/css',
});

This copies index.css to _site/css/index.css. Reference it in your template with:

<link rel="stylesheet" href="/css/index.css">

For community package CSS, add their stylesheets to the cssFiles array:

cssFiles: [
  'node_modules/@refrakt-md/lumina/index.css',
  'node_modules/@refrakt-md/marketing/styles/index.css',
],

Behavior Initialization

Interactive runes (tabs, accordion, datatable, etc.) need client-side JavaScript from @refrakt-md/behaviors. Copy the behaviors bundle to your output and initialize it in the template:

// add passthrough copy for behaviors
eleventyConfig.addPassthroughCopy({
  'node_modules/@refrakt-md/behaviors/dist/index.js': 'js/behaviors.js',
});

The template script block registers web component elements, sets the current URL for active-link behaviors, and initializes rune and layout behaviors:

<script type="module">
  import { registerElements, RfContext, initRuneBehaviors, initLayoutBehaviors } from '/js/behaviors.js';

  RfContext.currentUrl = window.location.pathname;
  registerElements();
  initRuneBehaviors();
  initLayoutBehaviors();
</script>
note

@refrakt-md/behaviors is optional. Without it, the page renders correctly but interactive runes will not have JavaScript enhancement.

ESM Compatibility

Eleventy 3.0 is ESM-native. The @refrakt-md/eleventy package, data files, and configuration files all use ES module syntax (import/export). Ensure your package.json has "type": "module".

Differences from Other Adapters

FeatureEleventySvelteKitHTML
Build toolEleventy CLIViteCustom script
Template languageNunjucks/LiquidSvelteNone (API)
Dev server--serve (live reload)vite dev (HMR)Manual
Component overridesNoYes (Svelte)No
Data loadingGlobal data fileVite plugin + virtual modulesDirect API call
OutputStatic HTMLSSR + SPAStatic HTML
Client routingNo (full page loads)Yes (SvelteKit router)No

The Eleventy adapter sits between the HTML adapter and the SvelteKit adapter in complexity. It provides Eleventy's template system and data cascade without requiring a bundler, while the HTML adapter gives you a raw API and the SvelteKit adapter gives you a full application framework.

Dependencies

PackageRequiredPurpose
@refrakt-md/contentYesContent loading, routing, layout cascade, cross-page pipeline
@refrakt-md/transformYesIdentity transform engine, layout transform, renderToHtml
@refrakt-md/typesYesShared TypeScript interfaces
@11ty/eleventyPeerEleventy v3 (ESM)
@refrakt-md/behaviorsOptionalClient-side progressive enhancement for interactive runes

Theme Integration

Lumina provides a dedicated Eleventy adapter export:

import manifest from '@refrakt-md/lumina/manifest';
import { layouts } from '@refrakt-md/lumina/layouts';
const theme = { manifest, layouts };

This export bundles the theme manifest and layout configurations (default, docs, blog-article) so you can pass it directly to createDataFile. Custom themes can implement the EleventyTheme interface by providing a manifest and layout map.