Theming
Introduction
By providing your own theme, you can customize the user experience for your own ownCloud installation. This is being achieved by providing a json file that contains text snippets (like brand name and slogan), paths to images (e.g. logos or favicon) and design tokens for various color, sizing and spacing parameters.
This page documents the setup and configuration options, and provides a template for you to get started.
Ways of Providing a Theme
Generally, your theming configuration lives inside a .json file, e.g. theme.json. To load this file, it needs to be correctly referenced inside your config/config.json (example configurations can be found on GitHub).
To reference your theme, you have two options:
-
Using a URL, e.g.
"theme": "https://externalurl.example.com/theme-name/theme.json",. To avoid CORS issues, please make sure that you host the URL on the same URL as your ownCloud web hosting. -
For development and testing purposes, you can store your
theme.jsoninsidepackages/web-runtime/themes/{theme-name}/and reference it in theconfig.json. However, this isn’t recommended for production use since your changes may get lost when updating oCIS.
Hint: If no theme is provided, the loading of your custom theme fails or the theme can’t be parsed correctly, the standard ownCloud theme will be loaded as a fallback and an error with further information will be logged on the browser console.
Configuring a Theme
Inside your theme.json, there is a common key, which is explained in the next section, and a clients key: Here, you can find the available ownCloud clients - please note that the documentation below focuses on web and check the respective documentation for other clients for details on their themability.
The general top-level structure of a valid theme.json is outlined below:
{
"common": {},
"clients": {
"android": {},
"desktop": {},
"ios": {},
"web": {}
}
}
Common Section
The common section provides a set of information that is designed to be available for all clients. It is used as the common entry of the web defaults and aims to reduce duplication. Note that this is a shallow replacement, not a merge: if clients.web.defaults.common is set, it replaces the top-level common section entirely. The resulting common can then be overwritten by more specific information inside the actual themes.
The structure of a valid common section is outlined below:
"common": {
"name": "ownCloud",
"slogan": "ownCloud – A safe home for all your data",
"logo": "themes/owncloud/assets/logo.svg",
"urls": {
"accessDeniedHelp": "",
"imprint": "",
"privacy": ""
},
"shareRoles": {}
}
All of the below parameters are required:
-
namespecifies the publicly visible name -
sloganspecifies the publicly visible slogan -
logospecifies the logo in e.g. the top bar within the web UI -
urls.accessDeniedHelpspecifies the target URL for the access denied help link -
urls.imprintspecifies the target URL for the imprint link -
urls.privacyspecifies the target URL for the privacy link -
shareRolesmaps share role IDs to an object with theiconNameused for that role
The following urls keys are optional:
-
urls.accessibilityStatementspecifies the target URL for the accessibility statement link -
urls.universalAccessEasyLanguagespecifies the target URL for the easy language page -
urls.universalAccessSignLanguagespecifies the target URL for the sign language page -
urls.softwareLicensespecifies the target URL for the software license link -
urls.helpPagespecifies the target URL for the help page link
Web Theme
The structure of a valid web client section is outlined below:
{
"web": {
"defaults": {
"appBanner": {
// Please see below for details
},
"common": {
// Please see top level "common" section for details
},
"logo": {
// Please see below for details
},
"loginPage": {
// Please see below for details
},
"designTokens": {
// Please see below for details
},
"icons": {
// Please see below for details
}
},
"themes": [
// Your custom web themes go here, see below for details
]
}
}
The "Defaults"
Similar to the top level common section, this object contains information that shall be shared among the available themes and can/should be defined only once. The top level common section is set as the common entry of the defaults unless defaults.common is provided, in which case defaults.common replaces it entirely. The defaults are then deep-merged into each individual theme, where values from the theme take precedence and arrays (e.g. tagColorsList) in a theme replace the ones from the defaults instead of being appended.
The "appBanner" Options
Configures a app banner that gets shown on mobile devices and suggests downloading the native client from the respective app store. Omitting the key disables the banner.
Example structure:
{
"appBanner": {
"title": "ownCloud",
"publisher": "ownCloud GmbH",
"additionalInformation": "",
"ctaText": "OPEN",
"icon": "themes/owncloud/assets/owncloud-app-icon.png",
"appScheme": "owncloud"
}
}
-
title
Is usually your app’s name as shown in the App Store or Google Play.publisheris the app developer’s name. -
additionalInformation
Can be used to specify pricing information, such as "FREE" or a catchphrase like "Don’t miss out on our awesome app!". -
ctaText
Refers to the text in the call to action button on the right side. Theicondirective may be used to specify your own app icon. -
icon
Links the icon to be displayed as a preview for the final app icon within the app banner -
appScheme
Is the first part of the URL that is used to tell the mobile OS which app to open, so usingownCloudwill generate links such asowncloud://yourdomain.com/f/2b61b822….
The "logo" Options
Here, you can specify the images to be used in the "topbar", for the "favicon" and on the "login" page. Various formats are supported and it’s up to you to decide which one fits your use case best.
"logo": {
"topbar": "themes/owncloud/assets/logo.svg",
"topbarSm": "themes/owncloud/assets/logo-sm.svg",
"favicon": "themes/owncloud/assets/favicon.jpg",
"login": "themes/owncloud/assets/logo.svg",
"href": "https://example.com"
},
The topbar, favicon and login keys are required. The following keys are optional:
-
topbarSmspecifies the top bar logo used on small screens -
hrefspecifies a URL the top bar logo links to; if not set, the logo links to the Web home page -
notFoundis accepted by the theme schema but currently not used by Web
The "icons" Options
Optional icons shown in the universal access menu of the top bar. The universal access menu is only displayed when urls.universalAccessEasyLanguage or urls.universalAccessSignLanguage is set in the common section.
"icons": {
"universalAccess": "",
"universalAccessEasyLanguage": "",
"universalAccessSignLanguage": ""
},
-
universalAccessspecifies the icon of the universal access menu button -
universalAccessEasyLanguagespecifies the icon of the easy language entry -
universalAccessSignLanguagespecifies the icon of the sign language entry
The "loginPage" Options
You can set the background image for the login page by providing an image file in the "backgroundImg" option.
"loginPage": {
"backgroundImg": "themes/owncloud/assets/loginBackground.jpg"
},
The "designTokens" Options
To further customize your ownCloud instance, you can provide your own styles. To give you an idea of how a working design system looks like, feel free to head over to the ownCloud design tokens for inspiration.
Hint: All the variables are initialized using the ownCloud design tokens and then overwritten by your theme variables. Therefore, you don’t have to provide all the variables and can use the default ownCloud colors as a fallback.
In general, the theme loader looks for a designTokens key inside your theme configuration. Inside the designTokens, it can find the collections and values outlined below, all of which are optional. The structure is outlined below:
"designTokens": {
"breakpoints": {
// Please see below for details
},
"colorPalette": {
// Please see below for details
},
"fontFamily": "", // Please see below for details
"fontSizes": {
// Please see below for details
},
"sizes": {
// Please see below for details
},
"spacing": {
// Please see below for details
},
"tagColorsList": [
// Please see below for details
]
}
Breakpoints
If you’d like to set different breakpoints than the default ones in the ownCloud design system, you can set them using theming variables.
Breakpoint variables get prepended with --oc-breakpoint-, so e.g. "large-default" creates the custom CSS property --oc-breakpoint-large-default.
{
"breakpoints": {
"xsmall-max": "",
"small-default": "",
"small-max": "",
"medium-default": "",
"medium-max": "",
"large-default": "",
"large-max": "",
"xlarge": ""
}
}
Colors
For the color values, you can use any valid CSS color format, e.g. hex (#fff), rgb (rgb(255,255,255)) or color names (white).
Color variables get prepended with --oc-color-, so e.g. "background-default" creates the custom CSS property --oc-color-background-default.
Again, you can use the ownCloud design tokens as a reference implementation.
{
"colorPalette": {
"background-accentuate": "",
"background-default": "",
"background-highlight": "",
"background-muted": "",
"border": "",
"input-bg": "",
"input-border": "",
"input-text-default": "",
"input-text-muted": "",
"swatch-brand-default": "",
"swatch-brand-hover": "",
"swatch-brand-muted": "",
"swatch-brand-contrast": "",
"swatch-danger-default": "",
"swatch-danger-hover": "",
"swatch-danger-muted": "",
"swatch-danger-contrast": "",
"swatch-inverse-default": "",
"swatch-inverse-hover": "",
"swatch-inverse-muted": "",
"swatch-passive-default": "",
"swatch-passive-hover": "",
"swatch-passive-hover-outline": "",
"swatch-passive-muted": "",
"swatch-passive-contrast": "",
"swatch-primary-default": "",
"swatch-primary-hover": "",
"swatch-primary-muted": "",
"swatch-primary-muted-hover": "",
"swatch-primary-gradient": "",
"swatch-primary-gradient-hover": "",
"swatch-primary-contrast": "",
"swatch-success-default": "",
"swatch-success-hover": "",
"swatch-success-muted": "",
"swatch-success-contrast": "",
"swatch-warning-default": "",
"swatch-warning-hover": "",
"swatch-warning-muted": "",
"swatch-warning-contrast": "",
"text-default": "",
"text-inverse": "",
"text-muted": ""
}
}
Font Sizes
You can change the default, large and medium font sizes according to your needs. If you need more customization options regarding font sizes, please open an issue on GitHub with a detailed description.
Font size variables get prepended with --oc-font-size-, so e.g. "default" creates the custom CSS property --oc-font-size-default.
{
"fontSizes": {
"default": "",
"large": "",
"medium": ""
}
}
Font Family
You can change the font family according to your needs. The font family gets written into the --oc-font-family CSS variable.
{
"fontFamily": ""
}
Please note that you also need to make the font available as a font-face via CSS.
Sizes
Use sizing variables to change various UI elements, such as icon and logo appearance, table row or checkbox sizes, according to your needs. If you need more customization options regarding sizes, please open an issue on GitHub with a detailed description.
Size variables get prepended with --oc-size-, so e.g. "icon-default" creates the custom CSS property --oc-size-icon-default.
{
"sizes": {
"form-check-default": "",
"height-small": "",
"height-table-row": "",
"icon-default": "",
"max-height-logo": "",
"max-width-logo": "",
"width-medium": "",
"tiles-default": "",
"tiles-resize-step": ""
}
}
Spacing
Use the six spacing options (xsmall | small | medium | large | xlarge | xxlarge) to create a more (or less) condensed version of the user interface. If you need more customization options regarding sizes, please open an issue on GitHub with a detailed description.
Spacing variables get prepended with --oc-spacing-, so e.g. "xlarge" creates the custom CSS property --oc-spacing-xlarge.
|
The stylesheets currently consume |
{
"spacing": {
"xsmall": "",
"small": "",
"medium": "",
"large": "",
"xlarge": "",
"xxlarge": ""
}
}
Tag Colors
Use tagColorsList to define the ordered list of colors tags are drawn from (at most 512 entries). Each entry gets written into a custom CSS property --oc-color-tag-<index>, starting at 0. The color of a tag is picked by hashing the tag name modulo the length of the list, so every theme should provide the same number of entries, otherwise a tag changes its color when switching themes.
For each entry, a matching text color is written into --oc-color-tag-<index>-text. It is whichever of the theme’s text-default and text-inverse colors contrasts better with the tag color, falling back to black and white if these are not hex colors. If the tag color itself is not a hex color, no text color property is written.
{
"tagColorsList": ["#e9f3fd", "#fdeee9", "#e9fdf0"]
}
Actual Themes
Apart from the defaults, you need to provide one or more themes in the themes key within the web-clients in your theme.json. As a reminder, the general structure should be:
{
"common": { ... },
"clients": {
...,
"web": {
"defaults": {
...
},
"themes": [
{
"isDark": false,
"name": "Light Theme",
}
]
}
}
}
Again, the defaults (including the resulting common section, see above) get merged into your themes, but locally provided information takes precedence.
Required information
-
name
For the visible name in the theme switcher and to save the current theme to localStorage -
isDark
To provide the user agent with additional information
Optional information
-
mode
Eitherregular(default) orvault. Themes with modevaultare only offered while the user is in the vault area of Web (URLs under/vault), all other themes only outside of it -
appBanner
See section above -
common
See section above -
designTokens
See section above -
icons
See section above -
logo
See section above -
loginPage
See section above
Extendability
If you define different key-value pairs inside any of the objects (breakpoints, colorPalette, fontSizes, sizes, spacing) or entries to tagColorsList in "designTokens", they will get loaded and initialized as CSS custom properties but don’t necessarily take any effect in the user interface. This gives you an opportunity to, for example, customize extensions from within the theme in the web runtime (and not the extension itself).
Example Theme
A full template for your custom theme is provided below, and you can use the instructions above to set it up according to your needs:
{
"common": {
"name": "ownCloud",
"slogan": "ownCloud – A safe home for all your data",
"logo": "themes/owncloud/assets/logo.svg",
"urls": {
"accessDeniedHelp": "",
"imprint": "",
"privacy": ""
},
"shareRoles": {
"b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5": {
"name": "UnifiedRoleViewer",
"iconName": "eye"
},
"a8d5fe5e-96e3-418d-825b-534dbdf22b99": {
"label": "UnifiedRoleSpaceViewer",
"iconName": "eye"
},
"2d00ce52-1fc2-4dbc-8b95-a73b73395f5a": {
"label": "UnifiedRoleFileEditor",
"iconName": "pencil"
},
"fb6c3e19-e378-47e5-b277-9732f9de6e21": {
"label": "UnifiedRoleEditor",
"iconName": "pencil"
},
"58c63c02-1d89-4572-916a-870abc5a1b7d": {
"label": "UnifiedRoleSpaceEditor",
"iconName": "pencil"
},
"312c0871-5ef7-4b3a-85b6-0e4074c64049": {
"label": "UnifiedRoleManager",
"iconName": "user-star"
},
"1c996275-f1c9-4e71-abdf-a42f6495e960": {
"label": "UnifiedRoleUploader",
"iconName": "pencil"
},
"aa97fe03-7980-45ac-9e50-b325749fd7e6": {
"label": "UnifiedRoleSecureView",
"iconName": "shield"
}
}
},
"clients": {
"android": {},
"desktop": {},
"ios": {},
"web": {
"defaults": {
"logo": {
"topbar": "themes/owncloud/assets/logo.svg",
"favicon": "themes/owncloud/assets/favicon.jpg",
"login": "themes/owncloud/assets/logo.svg"
},
"loginPage": {
"backgroundImg": "themes/owncloud/assets/loginBackground.jpg"
},
"designTokens": {
"breakpoints": {
"xsmall-max": "",
"small-default": "",
"small-max": "",
"medium-default": "",
"medium-max": "",
"large-default": "",
"large-max": "",
"xlarge": ""
},
"fontSizes": {
"default": "",
"large": "",
"medium": ""
},
"sizes": {
"form-check-default": "",
"height-small": "",
"height-table-row": "",
"icon-default": "",
"max-height-logo": "",
"max-width-logo": "",
"width-medium": "",
"tiles-default": "",
"tiles-resize-step": ""
},
"spacing": {
"xsmall": "",
"small": "",
"medium": "",
"large": "",
"xlarge": "",
"xxlarge": ""
}
}
},
"themes": [
{
"isDark": false,
"name": "Light Theme",
"designTokens": {
"colorPalette": {
"background-accentuate": "rgba(255, 255, 5, 0.1)",
"background-default": "#ffffff",
"background-highlight": "#edf3fa",
"background-muted": "#f8f8f8",
"background-secondary": "#ffffff",
"background-hover": "rgb(236, 236, 236)",
"components-apptopbar-background": "transparent",
"components-apptopbar-border": "#ceddee",
"border": "#ecebee",
"input-bg": "#ffffff",
"input-border": "#ceddee",
"input-text-default": "#041e42",
"input-text-muted": "#4c5f79",
"swatch-brand-default": "#041e42",
"swatch-brand-hover": "#223959",
"swatch-brand-contrast": "#ffffff",
"swatch-danger-contrast": "#ffffff",
"swatch-danger-default": "rgb(197, 48, 48)",
"swatch-danger-hover": "#b12b2b",
"swatch-danger-muted": "rgb(204, 117, 117)",
"swatch-inverse-default": "#ffffff",
"swatch-inverse-hover": "#ffffff",
"swatch-inverse-muted": "#bfbfbf",
"swatch-passive-default": "#4c5f79",
"swatch-passive-hover": "#43536b",
"swatch-passive-hover-outline": "#f7fafd",
"swatch-passive-muted": "#283e5d",
"swatch-passive-contrast": "#ffffff",
"swatch-primary-default": "#4a76ac",
"swatch-primary-hover": "#80a7d7",
"swatch-primary-muted": "#2c588e",
"swatch-primary-muted-hover": "rgb(36, 75, 119)",
"swatch-primary-gradient": "#4e85c8",
"swatch-primary-gradient-hover": "rgb(59, 118, 194)",
"swatch-primary-contrast": "#ffffff",
"swatch-success-default": "rgb(3, 84, 63)",
"swatch-success-hover": "#023b2c",
"swatch-success-muted": "rgb(83, 150, 10)",
"swatch-success-contrast": "#ffffff",
"swatch-warning-default": "rgb(183, 76, 27)",
"swatch-warning-hover": "#a04318",
"swatch-warning-muted": "rgba(183, 76, 27, .5)",
"swatch-warning-contrast": "#ffffff",
"text-default": "#041e42",
"text-inverse": "#ffffff",
"text-muted": "#4c5f79",
"icon-folder": "#4d7eaf",
"icon-archive": "#fbbe54",
"icon-image": "#ee6b3b",
"icon-spreadsheet": "#15c286",
"icon-document": "#3b44a6",
"icon-video": "#045459",
"icon-audio": "#700460",
"icon-presentation": "#ee6b3b",
"icon-pdf": "#ec0d47"
}
}
},
{
"isDark": true,
"name": "Dark Theme",
"designTokens": {
"colorPalette": {
"background-accentuate": "#696969",
"background-default": "#292929",
"background-highlight": "#383838",
"background-muted": "#383838",
"background-secondary": "#4f4f4f",
"background-hover": "#383838",
"components-apptopbar-background": "transparent",
"components-apptopbar-border": "#ceddee",
"border": "#383838",
"input-bg": "#4f4f4f",
"input-border": "#696969",
"input-text-default": "#dadcdf",
"input-text-muted": "#bdbfc3",
"swatch-brand-default": "#212121",
"swatch-brand-hover": "#ffffff",
"swatch-brand-contrast": "#dadcdf",
"swatch-inverse-default": "",
"swatch-inverse-hover": "",
"swatch-inverse-muted": "#696969",
"swatch-passive-default": "#c2c2c2",
"swatch-passive-hover": "",
"swatch-passive-hover-outline": "#3B3B3B",
"swatch-passive-muted": "#bdbfc3",
"swatch-passive-contrast": "#000000",
"swatch-primary-default": "#73b0f2",
"swatch-primary-hover": "#7bafef",
"swatch-primary-muted": "",
"swatch-primary-muted-hover": "#2282f7",
"swatch-primary-gradient": "#4e85c8",
"swatch-primary-gradient-hover": "#76a1d5",
"swatch-primary-contrast": "#dadcdf",
"swatch-success-background": "rgba(0, 188, 140, 0)",
"swatch-success-default": "rgb(0, 188, 140)",
"swatch-success-hover": "#00f0b4",
"swatch-success-muted": "rgba(0, 188, 140, .5)",
"swatch-success-contrast": "#000000",
"swatch-warning-background": "rgba(0,0,0,0)",
"swatch-warning-default": "rgb(232, 191, 73)",
"swatch-warning-hover": "#eed077",
"swatch-warning-muted": "rgba(232, 178, 19, .5)",
"swatch-danger-default": "rgb(255, 72, 53)",
"swatch-danger-hover": "#ff7566",
"swatch-danger-muted": "rgba(255, 72, 53, .5)",
"swatch-danger-contrast": "#dadcdf",
"swatch-warning-contrast": "#000000",
"text-default": "#dadcdf",
"text-inverse": "#000000",
"text-muted": "#c2c2c2",
"icon-folder": "rgb(44, 101, 255)",
"icon-archive": "rgb(255, 207, 1)",
"icon-image": "rgb(255, 111, 0)",
"icon-spreadsheet": "rgb(0, 182, 87)",
"icon-document": "rgb(44, 101, 255)",
"icon-video": "rgb(0, 187, 219)",
"icon-audio": "rgb(208, 67, 236)",
"icon-presentation": "rgb(255, 64, 6)",
"icon-pdf": "rgb(225, 5, 14)"
}
}
}
]
}
}
}