Default theme reference
The built-in theme lives at
src/public/themes/degoog-theme/
— use it as your starting point.
Getting started
Create a folder in data/themes/ (or
DEGOOG_THEMES_DIR). The folder name is your theme id. Add
a theme.json with at least a name.
| Tier | What you provide | What you control |
|---|---|---|
| CSS only | theme.json + stylesheet |
Colours, fonts, spacing via CSS variables |
| CSS + templates | theme.json + stylesheet + template files |
Above + HTML structure of any section |
| Layout override |
theme.json + stylesheet + custom
layout.html
|
Above + page title, meta tags, favicons, scripts |
| Full HTML override |
theme.json + stylesheet + custom
index.html / search.html
|
Total control — bypasses the layout entirely |
Only include the templates you want to change — everything else falls back to the default theme. Apply via Settings → Themes → Apply.
Layout override
To customise the page title, meta description, favicons, or anything
in the <head>, provide a
layout.html in your theme and reference it under
html.layout. Copy the
default layout
and edit it. You must keep these placeholders:
-
__PAGE_CONTENT__— skeleton HTML gets inserted here __BODY_CLASS__— body class attribute-
__THEME_TEMPLATES__— templates get injected here -
__THEME_CSS__,__PLUGIN_ASSETS__,__APP_VERSION__
Page architecture
Every page has three layers:
-
Layout
(
layout.html) — shared HTML shell. Change at your own risk. -
Skeleton
(
index.html/search.html) — empty<div>s with required IDs. JS depends on these. - Templates — individual HTML files rendered into the skeleton. These are what themes override.
theme.json
See the default theme.json for a complete example.
| Key | Required | Description |
|---|---|---|
name |
yes | Display name in Settings → Themes |
author, description,
version
|
no | Shown on the theme card |
css |
no |
Stylesheet path (supports .scss). Served at
/theme/style.css
|
templates |
no |
Object mapping template keys to file paths. Injected as
<template> elements automatically
|
html |
no |
Object with layout, index,
search. Fragments are composed into the layout;
full HTML documents (starting with
<!doctype>) bypass it entirely
|
settingsSchema |
no |
Exposes a Configure button on the theme card. Values stored
under theme-<id>
|
Custom HTML can use these placeholders:
__THEME_CSS__, __THEME_ATTRS__,
__PLUGIN_ASSETS__, __APP_VERSION__,
__THEME_TEMPLATES__, __PAGE_CONTENT__,
__BODY_CLASS__.
Static assets
Any file inside your theme folder is served at
/themes/<theme-id>/<path>. Supported types:
JS, CSS, HTML, JSON, SVG, PNG, JPG, GIF, WebP, TTF, WOFF, WOFF2.
Use this to load custom fonts, images, or scripts from your theme CSS or HTML:
@font-face {
font-family: "My Font";
src: url("/themes/my-theme/fonts/my-font.woff2") format("woff2");
font-display: swap;
}
theme.json is never served.
Example
{
"name": "My Theme",
"css": "style.css",
"templates": {
"result": "templates/result.html"
}
}
Templates
Templates control the HTML of every page section. List them under
templates in theme.json — keys are
auto-prefixed with degoog- to form the element id. The
last template with a given id wins, so your overrides take priority
over the defaults.
Search page templates
| Key | Description | Source |
|---|---|---|
search-header |
Logo + search bar + settings gear | header.html |
search-tabs |
Tab bar + options dropdown | tabs.html |
search-media-preview |
Image / video preview panel | media-preview.html |
search-lightbox |
Full-screen image lightbox | lightbox.html |
result |
Web / news result item | result.html |
image-card |
Image grid card | image-card.html |
video-card |
Video grid card | video-card.html |
at-a-glance |
At-a-glance box | at-a-glance.html |
Home page templates
| Key | Description | Source |
|---|---|---|
home-header |
Top header bar | header.html |
home-logo |
Logo / branding | logo.html |
home-search |
Search form | search.html |
home-footer |
Footer | footer.html |
Skeleton IDs
Skeleton elements are empty <div>s with only an
id. JS injects templates into them.
Do not remove or rename these IDs.
Home page
| ID | Receives |
|---|---|
header |
degoog-home-header |
main-home |
Main content area (plugins may inject here) |
home-logo |
degoog-home-logo |
home-search |
degoog-home-search |
home-footer |
degoog-home-footer |
Search page
| ID | Receives |
|---|---|
results-page |
Top-level wrapper |
results-header |
degoog-search-header |
results-tabs |
degoog-search-tabs |
results-meta |
Engine timing / stats |
results-layout |
Layout wrapper (main + sidebar + preview) |
results-main |
Main results column |
at-a-glance |
degoog-at-a-glance |
results-list |
Search results |
pagination |
Page navigation |
sidebar-col |
Sidebar column |
results-sidebar |
Sidebar content |
media-preview-panel |
degoog-search-media-preview |
img-lightbox |
degoog-search-lightbox |
slot-above-results |
Plugin slot |
slot-below-results |
Plugin slot |
slot-above-sidebar |
Plugin slot |
slot-below-sidebar |
Plugin slot |
Required IDs inside templates
Some templates contain inner elements that JS hooks into. Keep these
IDs when overriding or the feature breaks. Templates not listed here
(degoog-home-header, degoog-home-logo,
degoog-home-footer, degoog-result,
degoog-image-card, degoog-video-card,
degoog-at-a-glance) can be restructured freely.
degoog-home-search
search-input— text inputac-dropdown-home— autocomplete dropdownsearch-bar-actions-home— action buttons-
btn-lucky,lucky-slot-inner— lucky animation
degoog-search-header
results-search-input— search inputresults-search-btn— submit button-
ac-dropdown-results— autocomplete dropdown -
search-bar-actions-results— action buttons .results-logo(class) — logo link
degoog-search-tabs
-
.results-tab[data-type](class + attr) — tab buttons tools-bar— options wrappertools-toggle— dropdown toggletools-dropdown— dropdown menu-
tools-submenu-time,tools-submenu-lang— filter submenus -
tools-time-val,tools-lang-val— active filter labels -
tools-lang-filter,tools-lang-list— language filter -
tools-date-from,tools-date-to,tools-date-apply— date range
degoog-search-media-preview
-
media-preview-close,media-preview-prev,media-preview-next— nav buttons media-preview-img— preview imagemedia-preview-info— info panel
degoog-search-lightbox
img-lightbox-bg— backdropimg-lightbox-close— close buttonimg-lightbox-wrap— image containerimg-lightbox-img— full-size image
Placeholder syntax
Inside templates, use double curly braces for dynamic values. All values are HTML-escaped automatically.
| Syntax | Description |
|---|---|
{{ name }} |
Output a value |
{{#if name}} … {{/if name}} |
Conditional — rendered when truthy |
{{#each name}} … {{/each name}} |
Loop. {{ . }} = item, {{ @index }} =
index
|
{{#if}} blocks must contain balanced HTML. Placeholders
work in text and quoted attribute values (href="{{ url }}") but not as tag names.
Placeholders per template
degoog-result
| Key | Type | Description |
|---|---|---|
title |
string | Result title |
url |
string | Result URL |
cite_url |
string | Display URL (hostname + path) |
snippet |
string | Description |
favicon_url |
string | Proxied favicon |
thumbnail_url |
string | Proxied thumbnail (empty if none) |
sources |
array | Engine names |
duration |
string | Video duration (empty for non-video) |
link_target |
string | _blank or _self |
link_rel |
string | noopener or empty |
degoog-image-card
| Key | Type | Description |
|---|---|---|
title |
string | Alt text |
url |
string | Source page URL |
thumbnail_url |
string | Proxied thumbnail |
hostname |
string | Source hostname |
sources |
array | Engine names |
degoog-video-card
| Key | Type | Description |
|---|---|---|
title |
string | Video title |
url |
string | Video page URL |
thumbnail_url |
string | Proxied thumbnail |
hostname |
string | Source hostname |
duration |
string | Duration (may be empty) |
sources |
array | Engine names |
degoog-at-a-glance
| Key | Type | Description |
|---|---|---|
title |
string | Title |
url |
string | URL |
snippet |
string | Snippet text |
sources |
array | Engine names |
sources_text |
string | Engine names comma-separated |
Quick example
Override just the result template — everything else stays default:
templates/result.html:
<div class="my-result">
<a href="{{ url }}" target="{{ link_target }}">{{ title }}</a>
<p>{{ snippet }}</p>
<small>{{ cite_url }}</small>
{{#if thumbnail_url}}
<img src="{{ thumbnail_url }}" loading="lazy">
{{/if thumbnail_url}}
{{#each sources}}
<span class="tag">{{ . }}</span>
{{/each sources}}
</div>
theme.json:
{
"name": "My Theme",
"css": "style.css",
"templates": {
"result": "templates/result.html"
}
}