Skip to content

Repository files navigation

Axo

A static deck framework. Slides in .mdx, components in .axo, output is plain HTML with no runtime — no React, no hydration, no islands. A deck renders at a venue with no wifi, opens by double-click off a USB stick, and exports to PDF.

npx @ambystech/axo new my-talk --starter lightning --title "AmbyKit"
cd my-talk && npm install && npm run dev

A deck is thin

my-talk/
  slides/          one .mdx per slide      ← what you edit
  components/      your .axo components    ← optional
  layouts/         your .axo layouts       ← optional
  css/deck.css     this deck's CSS
  assets/
  axo.config.mjs   title, visibility, footer

That's all. The compiler, the 39 components, the 6 layouts, the theme, Reveal, the fonts and Mermaid all live in @ambystech/axo. npm update @ambystech/axo upgrades every deck you own — which is the difference between a framework and a folder you copied.

Commands

axo new <dir>     scaffold a deck    --starter --visibility --title --link --install --yes
axo dev           compile + watch + live-reload        --port 8000  --open
axo build         static site in dist/                 --force  (refuses if confidential)
axo preview       serve dist/                          --port 8080
axo pdf [out]     export to PDF (starts its own server, no flags)
axo check         compile without writing a site
axo ls            list every component and layout      --verbose
axo doctor        check the deck and the toolchain
axo clean         remove generated files

axo new with no flags asks — kind of deck, title, visibility — and can install for you. It never blocks a script: with --yes, no TTY, or in CI it takes the defaults and says so.

Not published yet? Point a deck at a local checkout:

axo new my-talk --link ../axo

A slide

---
layout: title
---

<Eyebrow>Ambystech · Lightning Talk</Eyebrow>

# AmbyKit

<Subtitle>Spec-Driven Development for AI coding assistants</Subtitle>

<Meta>
  <Field label="Speaker">Gustavo Barrientos</Field>
</Meta>

<Notes>
- Speaker notes. Not a script.
</Notes>

Components compose — you nest children, you don't pass data arrays:

<Matrix columns={['Spec-Kit', 'OpenSpec', 'AmbyKit']}>
  <Row label="Install"           cells={['Python', 'npm', 'npm']} />
  <Row label="Everyone has this" cells={['', '', '']} tie />
  <Row label="Only we do this"   cells={['', '', '']} reveal />
  <Row label="They do it better" cells={['', '', '— not yet']} concede reveal />
</Matrix>

Full component API: docs/components.md.

Writing .axo in VS Code, Cursor or another fork? The editor extension in extension/ gives you colour, completion and diagnostics — see docs/editor-support.md.

A component

.axo — a frontmatter script that runs at compile time, then a template. The filename is the component name. Drop it in components/; there is no registration step.

---
const { name, price, featured } = Axo.props;
---

<div class:list={['step', featured && 'featured']}>
  <span class="n">{name}</span>
  <h4>{price}</h4>
  <p><slot /></p>
</div>

Axo.props · Axo.children · Axo.slots.name · <slot /> · html:raw={} · class:list={[]}. expect · pick · cx · toChildArray are ambient — no imports, ever.

A deck component named after a built-in overrides it. Want a different Matrix? Put Matrix.axo in your deck. You never fork the framework, and everything else keeps upgrading.

No client:*, no islands, no runtime — by design. A client:load is a compile error. That's what keeps a deck working offline.

Visibility is a guard, not a label

A footer that says CONFIDENTIAL doesn't stop anyone publishing a deck; it only documents the leak. visibility in axo.config.mjs is the thing that does:

visibility axo build workflow delivery
public builds scaffolded, deploys to Pages public URL
internal builds, warns none behind auth / handover
confidential refuses (--force to override) none PDF only

Defaults: lightning → public · training → internal · commercial/proposalconfidential. It fails closed: an unset visibility is treated as confidential.

A non-public deck also gets <meta name="robots" content="noindex, nofollow, noarchive">, and no deploy workflow is written at all — there is nothing to trigger, and nothing to accidentally deploy.

TypeScript

The framework is written in TypeScript and ships compiled JavaScript plus .d.ts — you never need TypeScript to use it, and engines stays at Node >= 20.

npm run typecheck   # tsc --noEmit
npm run build       # esbuild -> dist/*.js  +  tsc -> dist/*.d.ts

build runs on prepare, so npm install / npm link produce a working dist/ with no extra step.

Deck authors get types too:

// axo.config.mjs
import { defineConfig } from '@ambystech/axo';

export default defineConfig({
  title: 'AmbyKit',
  visibility: 'public',   // autocompleted; a typo is a type error
});

What it compiles to

Static HTML. MDX, Preact and esbuild are compile-time dependencies of the framework; the browser never sees them. dist/ contains index.html, css/, assets/ and vendor/ — 2.6 MB, no CDN link anywhere, and every slide's markdown inlined so it opens over file://.

License

MIT — see LICENSE.

About

Create beautiful presentations with HTML/CSS/React components.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages