Skip to main content

Read the house style from another product

You run another product next to Nextcloud and want it to look the same. Read the active house style here: nineteen values and a logo cover a header, buttons, links and panels.

The OpenProject plugin openproject-thematiq reads this endpoint.

The endpoint​

GET /apps/thematiq/api/shell-tokens

You need no account and no session. The answer holds theme values only, the same ones every visitor already gets as CSS on the login page. A caller without a session may ask 120 times a minute.

{
"version": 1,
"id": "zuiddrecht",
"name": "Gemeente Zuiddrecht",
"revision": "bc30fd0d66100acf",
"tokens": {
"primary": "#3669A5",
"primary-deep": "#234A78",
"primary-light": "#EAF0F7",
"action": "#3669A5",
"action-deep": "#234A78",
"link": "#3669A5",
"accent": "#CC0000",
"ink": "#1B1C1D",
"text": "#3d3f43",
"muted": "#5E6168",
"line": "#E4E6EA",
"soft": "#EDEFF2",
"ground": "#F5F6F8",
"chip": "#F0F1F3",
"field": "#828A95",
"radius": "8px",
"radius-lg": "12px",
"font-family": "'Fira Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', Arial, sans-serif"
},
"logo": {
"url": "https://cloud.example.org/custom_apps/thematiq/img/logos/zuiddrecht.svg",
"inverse_url": "https://cloud.example.org/custom_apps/thematiq/img/logos/zuiddrecht-dark.svg"
}
}
  • id and name are the token set the instance wears. A set for one group, or a set in preview, is never answered.
  • revision changes when any value below it changes, and only then.
  • tokens always holds all nineteen roles. None is ever null or empty. menu was added on 14 October 2026; a reader that knows only the first eighteen can ignore it.
  • logo.url and logo.inverse_url are absolute URLs, or null when there is no such logo.

The nineteen roles​

RoleUse it for
primaryThe header bar and other brand surfaces
primary-deepThe darker step of primary: hover, borders
primary-lightA light tint of primary: selected rows, soft panels
actionThe main button, and only that. The set's action colour when it has one, else its primary
action-deepThe main button on hover, and its border
linkLinks and link-coloured ids
accentThe second brand colour. Red in many sets, so keep it for emphasis
inkHeadings and the strongest text
textBody text
mutedSecondary text
lineDividers and row lines
softA grey a step darker than chip: secondary buttons, table headers
groundThe page behind cards
chipChips, tags and hovered rows
fieldBorders of inputs
radiusCorners of buttons, chips and cards
radius-lgCorners of inputs, menus and dialogs
font-familyThe font stack
menuThe background of the main menu: the app navigation column in Nextcloud. White unless the set colours it

The colours are CSS colours, the two radii are CSS lengths, and font-family is a CSS font stack. The font files stay on Nextcloud: load the family yourself, or let the stack fall through to a system font.

A value an administrator set in the token editor wins over the token set's own value. A role the set does not fill is derived from a role next to it, or takes the stock Nextcloud value.

logo.url is the logo of the active set. When the set has none, it is the logo uploaded in Nextcloud's theming settings. logo.inverse_url is the logo for a dark surface, such as a header in primary. Expect null for either and keep your own logo as the fallback.

Ask again without downloading again​

The answer carries its revision as an ETag. Send it back and you get 304 with no body while nothing changed:

GET /apps/thematiq/api/shell-tokens
If-None-Match: "bc30fd0d66100acf"

Read the document when your product starts, keep it, and ask again every few minutes with If-None-Match. Do not read it on every page render.

After an update​

The path is new in this release. An instance that is updated the normal way answers on it at once. When the code was replaced without an update, Nextcloud keeps its route list for up to an hour and answers 404 until then. Restart the PHP workers to make it answer at once.

Next, request the document from your test instance and map the roles onto your product's own theme settings.

The shell sizes​

The OpenProject plugin draws its shell to a written canon of sizes, measured from the Nextcloud boards. npm run check:shell-canon -- --url <your instance> --user <admin> --password <password> measures thematiq's shell against the same canon on the Files page, and CI does the same with the Zuiddrecht set.

PartCanon
Top bar height68 px
Navigation width264 px
Navigation item height42 px
Table header row height44 px
Primary button height48 px
Table row height44 px
Body text15 px

The canon first had a 40 px primary button, a 48 px table row and 14 px body text. Nextcloud draws 48, 44 and 15. On 15 October 2026 Ruben decided that OpenProject follows Nextcloud, so these three are the canon now and thematiq does not change.