Decky is a professional, local Markdown presentation engine built with Go and htmx. It compiles standard Markdown files into sleek, interactive online slideshows featuring a synchronized presenter console, real-time Server-Sent Events (SSE) state synchronization, and self-contained PDF or HTML exporters.
- Blazing Fast Markdown Rendering: Uses Goldmark for GFM-compliant markdown compiling.
- Server-Side Code Highlighting: Syntax highlights code blocks using Chroma, compiling them into inline CSS styles for zero external dependency rendering.
- Aspect Ratio Lock: Strictly maintains a professional 16:9 layout viewport, auto-scaling to fit the browser window.
- Presenter Dashboard: Features a clock, elapsed timer, current/next slide previews, and real-time speaker notes display.
- SSE Real-Time Sync: Synchronizes slide navigation in real-time between the main viewer and presenter console.
- Self-Contained Export: Renders every slide via a locally installed headless Chrome and assembles them into a single, portable PDF that can be opened offline anywhere.
- Self-Contained HTML Export: Bundles every slide, plus all CSS and JS, into a single static HTML file that needs no server or network access to view.
- Deck-wide Custom CSS: A global
css:frontmatter key injects arbitrary CSS after the built-in stylesheet, so any rule can be overridden without forking the tool.
Ensure you have Go (version 1.22 or higher) installed.
You can install Decky directly using Go's package manager:
go install github.com/gophernment/decky@latestEnsure your Go bin directory ($GOPATH/bin or ~/go/bin) is added to your system's PATH.
Alternatively, clone the repository and install it locally:
git clone https://github.com/gophernment/decky.git
cd decky
go install .Decky provides four subcommands: serve, export, html, and usage.
Start the local HTTP server to display slides and enable presenter synchronization:
decky serve [-port 3325] example.mdThe port also defaults to the PORT environment variable when -port isn't given (-port always wins if both are set):
PORT=3000 decky serve example.md- Open the Presentation View at
http://localhost:3325/. - Open the Presenter Console at
http://localhost:3325/presenter.
Navigating slides on either window (using arrow keys, space, or buttons) will automatically sync the other window instantly via Server-Sent Events.
A fullscreen toggle button always sits in the bottom-right corner of the Presentation View. The prev/next nav buttons and the slide-number indicator (1 / 12) are hidden by default — enable them per deck with showControls / showSlideNumber in the global frontmatter (see Show/Hide Navigation Controls).
Press o (or the ⊞ button) for a thumbnail overview of every slide; click a thumbnail to jump there, Esc to close. Selecting a slide syncs the other window over SSE like any other navigation.
Export the presentation into a single self-contained PDF for distribution or offline use:
decky export [-o output.pdf] example.mdEach slide is rendered through a locally installed headless Chrome/Chromium (required for export; serve does not need it) and captured as a full-resolution image, one per PDF page — so the exported file looks exactly like the live view, including gradients, backgrounds, and syntax-highlighted code, with no server or browser needed to view it afterward.
Export the presentation into a single self-contained HTML file — no server required to view it:
decky html [-o output.html] example.mdEverything is inlined into the one output file (CSS, JS, and every slide's syntax-highlighted code), so it never needs network access at view time and can be hosted as a static page (e.g. GitHub Pages) or opened directly from disk. Unlike export, this does not require a local Chrome/Chromium install.
Write a local copy of the tutorial deck (USAGE.md) to disk — useful if you installed with go install and don't have the repo cloned:
decky usage [-o USAGE.md]Then view it live with decky serve USAGE.md.
Slides are separated by three hyphens (---) on a blank line:
# Slide One
Content goes here.
---
# Slide Two
Another slide.You can define slide properties using YAML frontmatter blocks at the beginning of the slides.
- Global settings should be placed on the first slide (Slide 0).
- Local settings override themes, layouts, or backgrounds for individual slides.
---
title: "Project Pitch"
theme: "slate"
layout: "cover"
background: "linear-gradient(135deg, #0f172a 0%, #1e293b 100%)"
color: "#ffffff"
---
# Main Cover Slidedefault: Normal vertical flex layout.cover: Centered cover page layout with gradient background support. Addalign: left(orright) to a slide to keep the large cover heading but stop centering the content.two-cols: Dual-column layout (useful for side-by-side text/images or text/code blocks).
A slide's title is just a Markdown heading — pick the level by how much room the slide can spare.
#(h1): high-impact title — large, with an accent rule and a generous gap below. Best forcoverslides, section dividers, and sparse slides.##(h2): compact title — accent-colored and nearly as large, but a small gap to the content. The default for content-dense slides (lists, tables, code,two-cols, splits). On atwo-colsslide a leading#/##spans both columns.###/####: sub-headings inside the slide body, not the slide title.- No heading: fine, and common — a full-bleed image, a pull quote, or a continuation slide. decky reduces the top padding so the content isn't stranded under an empty title band.
One title per slide — a # or a ##. Recolor the ## title with headingColors.h2 if the accent default doesn't suit the deck.
Emoji inside a gradient heading (a cover #, or any level given a gradient via headingColors) get clipped to the gradient and render blank or discolored. Wrap them so they keep their own colors: # <span class="no-clip">🚀</span> Launch Plan.
Put a css: block in the global frontmatter (Slide 0) for overrides that apply to every slide. It is emitted after decky's built-in stylesheet, so any rule you write wins the cascade. Works in serve, /presenter, export, and html.
---
title: "Project Pitch"
css: |
.slide table { border: 2px solid #0f172a; }
.slide h1 { letter-spacing: 0; }
---Set the deck's main fonts with fonts.sans / fonts.mono in the global frontmatter (first slide). Override just one slide's heading font with headerFont in that slide's local frontmatter — everything else on the slide keeps using the main font.
---
title: "Project Pitch"
fonts:
sans: 'Space Grotesk'
mono: 'JetBrains Mono'
---
# Uses the main font
---
headerFont: "Poppins, sans-serif"
---
# This slide's heading uses its own fontNote:
servemode (and/presenter) automatically link to Google Fonts for any custom font you set (fonts.sans,fonts.mono,headerFont), so most Google Font names just work — no extra setup needed. This does reach out to Google's CDN over the network.exportrenders with only locally-available or built-in fonts — if the font you named isn't installed on the machine runningexport, the PDF falls back to the built-in stack (Interfor sans,Fira Codefor mono) instead.
Each font field accepts a full comma-separated CSS font stack, not just one name. This is useful when your primary font has no glyphs for a script like Thai — list a script-specific font after it as a fallback:
---
title: "งานนำเสนอ"
fonts:
sans: "Poppins, 'Noto Sans Thai'"
---
# หัวข้อ (Poppins สำหรับ Latin, Noto Sans Thai สำหรับไทย)The browser tries each font in order per character, so Latin text renders in Poppins and Thai text automatically falls through to Noto Sans Thai. In serve/presenter, every real font name in the stack is fetched from Google Fonts (generic CSS keywords like sans-serif are ignored, not fetched).
The prev/next buttons and the slide-number indicator (1 / 12) in the Presentation View are hidden by default. Turn them on per deck with showControls / showSlideNumber in the global frontmatter (first slide):
---
title: "Project Pitch"
showControls: true
showSlideNumber: true
---
# Main Cover SlideThe fullscreen toggle button is always visible regardless of these settings. This config only affects serve's Presentation View — the Presenter Console and export output are unaffected.
Add speaker notes inside an HTML comment (<!-- ... -->) placed at the very bottom of the slide block:
# Slide Title
Slide content here.
<!--
Remember to mention the architecture schema here!
-->This project is licensed under the MIT License.