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.json inside packages/web-runtime/themes/{theme-name}/ and reference it in the config.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:

  • name specifies the publicly visible name

  • slogan specifies the publicly visible slogan

  • logo specifies the logo in e.g. the top bar within the web UI

  • urls.accessDeniedHelp specifies the target URL for the access denied help link

  • urls.imprint specifies the target URL for the imprint link

  • urls.privacy specifies the target URL for the privacy link

  • shareRoles maps share role IDs to an object with the iconName used for that role

The following urls keys are optional:

  • urls.accessibilityStatement specifies the target URL for the accessibility statement link

  • urls.universalAccessEasyLanguage specifies the target URL for the easy language page

  • urls.universalAccessSignLanguage specifies the target URL for the sign language page

  • urls.softwareLicense specifies the target URL for the software license link

  • urls.helpPage specifies 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. publisher is 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. The icon directive 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 using ownCloud will generate links such as owncloud://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:

  • topbarSm specifies the top bar logo used on small screens

  • href specifies a URL the top bar logo links to; if not set, the logo links to the Web home page

  • notFound is 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": ""
},
  • universalAccess specifies the icon of the universal access menu button

  • universalAccessEasyLanguage specifies the icon of the easy language entry

  • universalAccessSignLanguage specifies 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 --oc-space- properties, not --oc-spacing-. Spacing overrides therefore have no visible effect yet.

{
  "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
    Either regular (default) or vault. Themes with mode vault are 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)"
            }
          }
        }
      ]
    }
  }
}