Tintero Developers

Making a theme

A theme is one CSS file. What it must declare, what the installer does to it on the way in, and the two ways it differs from every stylesheet you have written before.

On this page

A theme is one stylesheet that gives Tintero’s own CSS variables different values. To try one you need no manifest, no build step and no plugin: write a .css file and load it from Settings → General → Style.

That is the whole mechanism. Everything below follows from it.

The smallest theme that works

:root {
  --dark-bg: #10131a;
  --dark-menu: #171b24;
  --dark-secondary: #151922;
  --dark-tertiary: #1e2430;
  --dark-quaternary: #2a3243;
  --text-primary: #d6e0f5;
  --text-secondary: #8ea2c6;
  --text-tertiary: #6b7c9c;
  --accent-color: #5b9dd9;
  --accent-color-hover: #7cb4e6;
  --border-color: #2a3243;
  --button-hover: #1e2430;
}

Those are the 12 variables that every theme shipped with Tintero sets. The theme variable reference lists all 157 of them, with a count of how many of the 29 built-in themes bother with each one.

Two ways this is not like a normal stylesheet

Some colours are written twice, and the second copy is not a colour

Tintero fades panels in and out by writing rgba(var(--dark-bg-rgba), 0.6). For that to work, --dark-bg-rgba has to hold three bare numbers rather than a colour:

:root {
  --dark-bg: #10131a;
  --dark-bg-rgba: 16, 19, 26;
}

11 variables come in pairs like that, and the built-in themes write both halves by hand. The reference page lists them.

Plugins get your colours too

Every plugin runs in its own sealed frame, and Tintero copies the current variables into each of them, then pushes updates when somebody switches theme. A plugin reading var(--text-primary) is reading yours.

So if your text and background do not contrast, you break plugin panels too, and those are the panels whose authors have no way to test against your theme. Contrast is not only about the editor.

The rest of the file

The variables are the part with rules attached. Everything else in the file is ordinary CSS and gets applied exactly as you wrote it. The template theme uses that for the things variables cannot reach:

:root {
  /* … your variables … */
}

/* Everything below is plain CSS against the real application DOM. */
::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-thumb {
  background: var(--accent-color);
  border-radius: 4px;
}

Testing it

  1. Save your file as theme.css.
  2. Settings → General → Style → Local themes → Upload theme, and pick it.
  3. Name it when prompted. It applies immediately.
  4. Edit and use Replace to reload, rather than uploading a second copy.

Tintero refuses the file if it is not a .css, if it is bigger than 500 KB, or if there are no braces in it anywhere. That is the entire check, which cuts both ways: a typo in a variable name is not an error, it is just a variable that quietly does nothing. If a colour will not take, check the spelling against the reference before you look anywhere else.

Packaging it for other people

Four files at the root of a repository:

theme.css      the stylesheet
theme.json     the manifest, below
preview.png    a screenshot, 800 × 600 recommended
README.md      what it looks like and who it is for

theme.json is what the browser inside the app reads about you:

{
  "name": "Midnight Harbour",
  "id": "midnight-harbour",
  "version": "1.0.0",
  "author": "Your Name",
  "description": "A cool, low-contrast dark theme for long nights",
  "repository": "https://github.com/you/midnight-harbour",
  "preview": "preview.png",
  "license": "MIT",
  "tags": ["dark", "cool", "minimal"],
  "colors": {
    "primary": "#5b9dd9",
    "background": "#10131a",
    "text": "#d6e0f5"
  }
}

Every field is required. colors is the three-swatch preview on your card, so make it match what the theme actually looks like rather than what you were going for. The theme manifest reference has the field types straight from the app.

Then read publishing a theme, which is where that version string has to start agreeing with two other places.