diff --git a/README.md b/README.md
index 54300eabe..69f00a458 100644
--- a/README.md
+++ b/README.md
@@ -419,7 +419,7 @@ The color schemes is based on the [Prism library](https://prismjs.com).

-The [Prism repository](https://github.com/PrismJS/prism-themes/tree/master?tab=readme-ov-file#available-themes) offers a wide range of themes to choose from as well as a [CDN option](https://unpkg.com/browse/prismjs@1.29.0/themes).
+The [automad-prism-themes repository](https://github.com/automadcms/automad-prism-themes) offers a wide range of themes to choose from as well as a [CDN option](https://unpkg.com/browse/automad-prism-themes@latest/dist/).
##### element
diff --git a/packages/screenshot/README.md b/packages/screenshot/README.md
index c7ba998be..bb6ab38a0 100644
--- a/packages/screenshot/README.md
+++ b/packages/screenshot/README.md
@@ -61,17 +61,17 @@ const buffer = await browserless.screenshot('https://example.com', {
### Options
-| Option | Type | Default | Description |
-|--------|------|---------|-------------|
-| `type` | `string` | `'png'` | Image format: `'png'`, `'jpeg'`, `'webp'` |
-| `quality` | `number` | — | Quality (0-100) for JPEG/WebP |
-| `fullPage` | `boolean` | `false` | Capture full scrollable page |
-| `element` | `string` | — | CSS selector for element screenshot |
-| `codeScheme` | `string` | `'atom-dark'` | Prism.js theme for code highlighting |
-| `waitUntil` | `string` | `'auto'` | When to consider navigation done |
-| `waitForDom` | `number` | `0` | DOM stability window in ms (idle is `waitForDom / 10`, `0` disables DOM wait) |
-| `isPageReady` | `function` | `({ isWhite }) => !isWhite` | Custom readiness predicate for retry loop |
-| `overlay` | `object` | `{}` | Browser overlay options |
+| Option | Type | Default | Description |
+| ------------- | ---------- | --------------------------- | ----------------------------------------------------------------------------- |
+| `type` | `string` | `'png'` | Image format: `'png'`, `'jpeg'`, `'webp'` |
+| `quality` | `number` | — | Quality (0-100) for JPEG/WebP |
+| `fullPage` | `boolean` | `false` | Capture full scrollable page |
+| `element` | `string` | — | CSS selector for element screenshot |
+| `codeScheme` | `string` | `'atom-dark'` | Prism.js theme for code highlighting |
+| `waitUntil` | `string` | `'auto'` | When to consider navigation done |
+| `waitForDom` | `number` | `0` | DOM stability window in ms (idle is `waitForDom / 10`, `0` disables DOM wait) |
+| `isPageReady` | `function` | `({ isWhite }) => !isWhite` | Custom readiness predicate for retry loop |
+| `overlay` | `object` | `{}` | Browser overlay options |
All [Puppeteer page.screenshot() options](https://pptr.dev/api/puppeteer.screenshotoptions) are supported.
@@ -84,10 +84,10 @@ const buffer = await browserless.screenshot(url, {
overlay: {
// Browser frame theme: 'dark' or 'light'
browser: 'dark',
-
+
// Background: color, gradient, or image URL
background: 'linear-gradient(225deg, #FF057C 0%, #8D0B93 50%, #321575 100%)',
-
+
// Margin around the screenshot (default: 0.2 = 20%)
margin: 0.2
}
@@ -149,8 +149,114 @@ const buffer = await browserless.screenshot('https://api.example.com/data', {
})
```
-Available themes from [prism-themes](https://github.com/PrismJS/prism-themes):
-- `atom-dark`, `ghcolors`, `dracula`, `duotone-dark`, `duotone-light`, `material-dark`, `material-light`, `nord`, `synthwave84`, and more
+Available themes from [automad-prism-themes](https://github.com/automadcms/automad-prism-themes) and [prism-themes](https://github.com/PrismJS/prism-themes). When a theme exists in both, the automad version is used.
+
+| Theme | Source |
+| --------------------------------- | ------------ |
+| `a11y-dark` | prism-themes |
+| `atom-dark` | automad |
+| `atom-one-dark` | automad |
+| `atom-one-light` | automad |
+| `aura-dark` | automad |
+| `automad-dark` | automad |
+| `automad-light` | automad |
+| `ayu-dark` | automad |
+| `ayu-light` | automad |
+| `ayu-mirage` | automad |
+| `base` | automad |
+| `base16-ateliersulphurpool.light` | prism-themes |
+| `bearded-arc-blueberry` | automad |
+| `bearded-vivid-light` | automad |
+| `boola-dark` | automad |
+| `boola-light` | automad |
+| `catppuccin-frappe` | automad |
+| `catppuccin-latte` | automad |
+| `catppuccin-macchiato` | automad |
+| `catppuccin-mocha` | automad |
+| `cb` | prism-themes |
+| `coldark-cold` | automad |
+| `coldark-dark` | automad |
+| `coy-without-shadows` | prism-themes |
+| `darcula` | prism-themes |
+| `dark-frost` | automad |
+| `dark-space` | automad |
+| `dracula` | automad |
+| `duotone-dark` | automad |
+| `duotone-earth` | automad |
+| `duotone-forest` | automad |
+| `duotone-light` | automad |
+| `duotone-sea` | automad |
+| `duotone-space` | automad |
+| `ghcolors` | prism-themes |
+| `github-dark` | automad |
+| `github-light` | automad |
+| `gruvbox-dark` | automad |
+| `gruvbox-light` | automad |
+| `holi-theme` | prism-themes |
+| `hopscotch` | prism-themes |
+| `laserwave` | automad |
+| `lucario` | prism-themes |
+| `material-dark` | prism-themes |
+| `material-light` | prism-themes |
+| `material-oceanic` | prism-themes |
+| `night-owl` | automad |
+| `night-owl-light` | automad |
+| `nightfall` | automad |
+| `nord` | automad |
+| `one-dark` | prism-themes |
+| `one-light` | prism-themes |
+| `panda` | automad |
+| `poimandres` | automad |
+| `pojoaque` | prism-themes |
+| `rose-pine` | automad |
+| `rose-pine-dawn` | automad |
+| `sakura-sun` | automad |
+| `sea-shells-dark` | automad |
+| `serendipity-midnight` | automad |
+| `serendipity-morning` | automad |
+| `serendipity-sunset` | automad |
+| `shades-of-purple` | automad |
+| `solarized-dark-atom` | automad |
+| `synthwave84` | automad |
+| `tailwind-ice` | automad |
+| `tailwind-moon-blue` | automad |
+| `tokyo-night` | automad |
+| `tokyo-night-light` | automad |
+| `tokyo-night-storm` | automad |
+| `verdandi` | automad |
+| `verdandi-alter` | automad |
+| `violet-dream` | automad |
+| `vs` | prism-themes |
+| `vsc-dark-plus` | automad |
+| `xonokai` | prism-themes |
+| `z-touch` | prism-themes |
+
+Some themes also offer light/dark combo variants that bundle both modes in a single CSS file, switching automatically based on the page's dark mode class. Since screenshots render without a dark mode context, the light variant will always be used — prefer explicit themes like `github-light` or `github-dark` instead.
+
+Available combo themes:
+
+| Combo theme | Light | Dark |
+| ---------------------------------- | --------------------- | ----------------------- |
+| `atom-one.light-dark` | `atom-one-light` | `atom-one-dark` |
+| `automad.light-dark` | `automad-light` | `automad-dark` |
+| `ayu.light-dark` | `ayu-light` | `ayu-dark` |
+| `ayu-mirage.light-dark` | `ayu-light` | `ayu-mirage` |
+| `bearded-arc-blueberry.light-dark` | `bearded-vivid-light` | `bearded-arc-blueberry` |
+| `boola.light-dark` | `boola-light` | `boola-dark` |
+| `catppuccin-frappe.light-dark` | `catppuccin-latte` | `catppuccin-frappe` |
+| `catppuccin-macchiato.light-dark` | `catppuccin-latte` | `catppuccin-macchiato` |
+| `catppuccin-mocha.light-dark` | `catppuccin-latte` | `catppuccin-mocha` |
+| `coldark.light-dark` | `coldark-cold` | `coldark-dark` |
+| `github.light-dark` | `github-light` | `github-dark` |
+| `gruvbox.light-dark` | `gruvbox-light` | `gruvbox-dark` |
+| `night-owl.light-dark` | `night-owl-light` | `night-owl` |
+| `rose-pine.light-dark` | `rose-pine-dawn` | `rose-pine` |
+| `serendipity-midnight.light-dark` | `serendipity-morning` | `serendipity-midnight` |
+| `serendipity-sunset.light-dark` | `serendipity-morning` | `serendipity-sunset` |
+| `tailwind-moon-blue.light-dark` | `tailwind-ice` | `tailwind-moon-blue` |
+| `tokyo-night-storm.light-dark` | `tokyo-night-light` | `tokyo-night-storm` |
+| `tokyo-night.light-dark` | `tokyo-night-light` | `tokyo-night` |
+| `verdandi.light-dark` | `verdandi` | `verdandi-alter` |
### Smart Content Detection
@@ -222,22 +328,22 @@ const buffer = await browserless.screenshot('https://example.com', {
This is a **core functionality package** for screenshot capture:
-| Consumer | Purpose |
-|----------|---------|
-| `browserless` (core) | Provides the `.screenshot()` method |
-| `@browserless/cli` | Powers the `browserless screenshot` command |
-| `@browserless/pdf` | Uses `isWhiteScreenshot` for content detection |
+| Consumer | Purpose |
+| -------------------- | ---------------------------------------------- |
+| `browserless` (core) | Provides the `.screenshot()` method |
+| `@browserless/cli` | Powers the `browserless screenshot` command |
+| `@browserless/pdf` | Uses `isWhiteScreenshot` for content detection |
### Dependencies
-| Package | Purpose |
-|---------|---------|
-| `@browserless/goto` | Page navigation with ad blocking |
-| `sharp` | Image composition for overlays; White/blank screenshot detection |
-| `prism-themes` | Syntax highlighting themes |
-| `svg-gradient` | Generate gradient backgrounds |
-| `got` | Fetch remote background images |
-| `is-html-content` | Detect if content is HTML vs code |
+| Package | Purpose |
+| ---------------------- | ---------------------------------------------------------------- |
+| `@browserless/goto` | Page navigation with ad blocking |
+| `sharp` | Image composition for overlays; White/blank screenshot detection |
+| `automad-prism-themes` | Syntax highlighting themes |
+| `svg-gradient` | Generate gradient backgrounds |
+| `got` | Fetch remote background images |
+| `is-html-content` | Detect if content is HTML vs code |
## License
diff --git a/packages/screenshot/package.json b/packages/screenshot/package.json
index b496d10e7..2cdc8f423 100644
--- a/packages/screenshot/package.json
+++ b/packages/screenshot/package.json
@@ -35,6 +35,7 @@
"@browserless/goto": "^10.11.4",
"@kikobeats/content-type": "~1.0.3",
"@kikobeats/time-span": "~1.0.11",
+ "automad-prism-themes": "~0.3.7",
"debug-logfmt": "~1.4.8",
"got": "~11.8.6",
"is-html-content": "~1.0.0",
diff --git a/packages/screenshot/src/pretty/theme.js b/packages/screenshot/src/pretty/theme.js
index 7b4c3012b..83e63aa7f 100644
--- a/packages/screenshot/src/pretty/theme.js
+++ b/packages/screenshot/src/pretty/theme.js
@@ -5,18 +5,46 @@ const { readFile } = require('fs/promises')
const isHttpUrl = require('is-url-http')
const path = require('path')
+const { existsSync, readdirSync } = require('fs')
+
const CACHE = new NullProtoObj()
+let themeIndex
-const GET_THEME_PATH = () => require('prism-themes').themesDirectory
+const GET_THEME_INDEX = () => {
+ const automadRoot = path.dirname(require.resolve('automad-prism-themes/package.json'))
+ const { themesDirectory } = require('automad-prism-themes')
+ const prismRoot = path.dirname(require.resolve('prism-themes/package.json'))
+ const { themesDirectory: prismThemesDirectory } = require('prism-themes')
-const THEME_PATH = () => CACHE.root || (CACHE.root = GET_THEME_PATH())
+ const dirs = [
+ themesDirectory,
+ path.resolve(automadRoot, 'dist'),
+ prismThemesDirectory,
+ path.resolve(prismRoot, 'themes')
+ ].filter(dir => dir && existsSync(dir))
-module.exports = async themeId => {
- if (isHttpUrl(themeId)) return ``
+ const index = new NullProtoObj()
+ for (const dir of dirs) {
+ for (const file of readdirSync(dir)) {
+ const match = file.match(/^prism-(.+)\.css$/)
+ if (match && !match[1].endsWith('.min') && !(match[1] in index)) {
+ index[match[1]] = path.resolve(dir, file)
+ }
+ }
+ }
+ return index
+}
- const stylesheet =
- CACHE[themeId] ||
- (CACHE[themeId] = await readFile(path.resolve(THEME_PATH(), `prism-${themeId}.css`)))
+const THEME_INDEX = () => themeIndex || (themeIndex = GET_THEME_INDEX())
- return ``
+const readTheme = async themeId => {
+ const filePath = THEME_INDEX()[themeId]
+ if (!filePath) throw new Error(`Unable to resolve Prism theme: ${themeId}`)
+ return readFile(filePath)
+}
+
+module.exports = async themeId => {
+ if (isHttpUrl(themeId)) return ``
+ CACHE[themeId] = CACHE[themeId] || (await readTheme(themeId))
+ return ``
}