How to Create a Theme for Cyca

This document will guide you through creating a theme for Cyca.

Install pre-requisites

To create a theme for Cyca, you will need to install a few things:

  • git
  • node.js (Cyca always use the LTS version)
  • Cyca’s code base
git clone https://github.com/RichardDern/Cyca

Install Cyca’s dependencies:

cd Cyca
npm i

A theme in Cyca is basically a tailwind configuration file. If you already know how to configure tailwind, it’s going to be really easy. Otherwise, you should look at tailwind’s documentation.

If you look at the resources/themes/baseTheme.js file, you will see some elements have already been taken care of. All you need to do in your theme is to extend this object.

So, let’s see how to create your theme.

Create the theme

Create a new directory in the resources/themes/ folder.

mkdir resources/themes/my_theme

The scructure of a theme is quite simple. In your theme folder, you should have the following files and sub-folders:

  • dist/ : This folder will be created automatically during the compilation process
  • resources/ : You will put in this folder (and maybe sub-folders) the resources your theme needs, like icons and fonts
  • theme.css : Which is the entry-point to the PostCSS parsing and compiling process
  • theme.js : Which contains tailwind’s configuration
  • theme.json : Which contains your theme’s meta-data, such as author, link, etc.

Let’s begin with the simplest file you could have: theme.css. All it needs is to import Cyca’s base stylesheet, so just add the following to this file:

@import "../../css/app.css";

Of course, you can add all the CSS you need after this line.

Next, create your theme.json file:

{
    "author": "Richard Dern",
    "name": "Cyca Dark",
    "description": "Cyca's default theme - Dark colors",
    "url": "https://github.com/RichardDern/cyca_theme_dark",
    "screenshot": "images/screenshot.png",
    "icons": "images/icons.svg",
    "inherits": null
}

theme.json reference

Key Data
author Author’s name of the theme
name Theme’s name
description Theme’s short description
url URL to your theme’s website or repository
screenshot Path relative to your theme’s resources/ directory to a screenshot of your theme
icons Path relative to your theme’s resources/ directory to the icons used by your theme
inherits Name of inherited theme

The screenshot file must be a valid image, either jpg or png, with a maximum size of 1280x720 pixels.

Icons reference

The icons file must be an single file of SVG sprites. The following symbol ids are used in Cyca:

Symbol id Represents
account Account’s link
add Add button’s icon
check Tick
checkmark Tick ribbon
collapsed Folder’s caret in collasped mode
expanded Folder’s caret in expanded mode
folder Regular folder icon
house The root directory
logout The logout icon
open Open button’s icon
unread_items Unread items folder
update Update button’s icon
share Share button’s icon

theme.js file

This is where you define your fonts, your color scheme, everything related to Cyca’s UI.

Let’s take Cyca’s default dark theme as an example:

// We will use lodash's merge method to recursively merge base theme object and ours
const _ = require('lodash');

// We will create a plugin to add a locally stored font
const plugin = require("tailwindcss/plugin");

// We begin the theme's definition by merging it with Cyca's provided base configuration
const theme = _.merge(require("../baseTheme.js"), {
    theme: {
        // Re-defining the "sans-serif" font family to add Quicksand, "our" custom font
        fontFamily: {
            sans:
                'Quicksand, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'
        },
        // Here, we import a colors file which contains our color scheme
        colors: require("./colors")
    },
    plugins: [
        // And we create a plugin to reference our font.
        // Note: the same could have been achieved directly in the CSS
        // /themes/cyca-dark/fonts is relative to Cyca's public/ directory
        plugin(function({ addBase, config }) {
            const fontUrl =
                "/themes/cyca-dark/fonts/Quicksand-Medium.ttf";

            addBase({
                "@font-face": {
                    fontFamily: "Quicksand",
                    src: "url(" + fontUrl + ")"
                }
            });
        })
    ]
});

// Finally, we export the module so tailwind can use it
module.exports = theme;

The Quicksand font is stored in theme’s directory structure, more precisely in (relative to Cyca’s root directory):

  • resources
    • themes
      • cyca-dark
        • resources
          • fonts
            • Quicksand-Medium.ttf

Everything in theme’s resources/ directory will be copied in the public/themes/theme_name/ directory during compilation.

Now, you may have noticed we require a colors.js file. You don’t know it yet, unless you’ve already looked at cyca-dark’s theme source, but colors.js itself calls the colorScheme.js file.

The colorScheme.js file stores the “database” of colors used in the theme. While not strictly required to have it appart, it’s a good practice to keep it separated from the rest of your theme and reference it when you need it. In this case, it’s just a module:

module.exports = {
    white: "#ffffff",
    red: {
        "800": "#9B2C2C",
        "900": "#742A2A"
    },
    yellow: {
        "500": "#ED8936"
    },
    green: {
        "400": "#68D391",
        "600": "#38A169",
        "800": "#276749",
        "900": "#22543D"
    },
    blue: {
        "400": "#63B3ED",
        "500": "#4299E1",
        "700": "#2B6CB0",
        "800": "#2C5282"
    },
    purple: {
        "500": "#9F7AEA"
    },
    "cool-gray": {
        "500": "#A0AEC0"
    },
    gray: {
        "100": "#5c5c5c",
        "200": "#525252",
        "300": "#474747",
        "400": "#3d3d3d",
        "500": "#333333",
        "600": "#292929",
        "700": "#1f1f1f",
        "800": "#141414",
        "900": "#0a0a0a"
    }
}

Tailwind will only generate color classes for color described here, so the final CSS will be lighter. Otherwise, it will use the whole default color palette and generate a quite large CSS.

Now, back to the colors.js file, which will map colors names used in Cyca’s base CSS files with colors defined in your theme.

// Include colors we just defined
const baseColors = require('./colorScheme');

/**
 * Colors used in the CSS by calling ```theme("colors.<named color>")```
 * For a hovered danger button background color, we will call
 * ```theme("colors.button.danger.bg-hover")``` in our CSS
 */
module.exports = {
    baseColors,
    /**
     * Base
     */
    a: {
        normal: baseColors.blue[400],
        hover: baseColors.blue[500]
    },
    article: {
        text: baseColors.white,
        bg: baseColors.gray[300],
        border: baseColors.gray[700],
        body: baseColors.white
    },
    body: {
        text: baseColors.white,
        bg: baseColors.gray[900]
    },
    dl: {
        text: baseColors.white
    },
    dt: {
        bg: baseColors.gray[700]
    },
    dd: {
        bg: baseColors.gray[400]
    },
    button: {
        text: baseColors.white,
        danger: {
            bg: baseColors.red[800],
            "bg-hover": baseColors.red[900]
        },
        success: {
            bg: baseColors.green[600],
            "bg-hover": baseColors.green[800]
        },
        info: {
            bg: baseColors.blue[700],
            "bg-hover": baseColors.blue[800]
        }
    },
    label: {
        text: baseColors.gray[100]
    },
    formGroup: {
        text: baseColors.white
    },
    input: {
        text: baseColors.white,
        bg: baseColors.gray[300]
    },
    scrollbar: baseColors.gray[300],
    /**
     * Components
     */
    badge: {
        text: baseColors.white,
        bg: baseColors.gray[300],
        "bg-article": baseColors.gray[500]
    },
    caret: baseColors.gray[100],
    "folders-tree": baseColors.gray[800],
    "documents-list": baseColors.gray[700],
    "feeditems-list": baseColors.gray[600],
    "details-view": baseColors.gray[500],
    "list-item": {
        text: baseColors.white,
        active: {
            text: baseColors.white,
            bg: baseColors.gray[400]
        },
        "dragged-over": {
            bg: baseColors.green[900]
        },
        "cannot-drop": {
            bg: baseColors.red[900]
        }
    },
    "feed-item": {
        text: baseColors.white,
        active: {
            text: baseColors.white,
            bg: baseColors.gray[400]
        },
        read: baseColors.gray[100],
        meta: baseColors["cool-gray"][500]
    },
    account: {
        menu: {
            bg: baseColors.gray[800],
            item: {
                text: baseColors.gray[300],
                hovered: baseColors.blue[500],
                selected: {
                    text: baseColors.white
                }
            }
        },
        content: {
            bg: baseColors.gray[700]
        }
    },
    folders: {
        common: baseColors.yellow[500],
        unread: {
            "not-empty": baseColors.purple[500],
            empty: baseColors.gray[100]
        },
        root: baseColors.blue[500],
        account: baseColors.green[600],
        logout: baseColors.red[800]
    },
    "themes-browser": {
        card: {
            "border-color": baseColors.gray[300],
            selected: {
                "border-color": baseColors.green[600],
            },
            hovered: {
                "border-color": baseColors.blue[500],
            }
        }
    },
    alerts: {
        success: {
            bg: baseColors.green[600],
            text: baseColors.white
        },
        error: {
            bg: baseColors.red[900],
            text: baseColors.white
        },
        warning: {
            bg: baseColors.yellow[500],
            text: baseColors.white
        }
    }
};

This is the most complete up-to-date example I can give you. It shows every customizable component, but things are getting a lot simpler when you inherit your theme from another one.

Themes inheritance

This time, I will use Cyca’s light theme as an example, as it inherits from default dark theme we just saw.

When your theme inherits from another one, and unless you make it otherwise, it will use parent theme’s configuration, including fonts, custom icons, colors, plugins, etc.

In the case of Cyca’s light theme, I inherited it from cyca-dark. So, its theme.json file looks like that:

{
    "author": "Richard Dern",
    "name": "Cyca Light",
    "description": "Cyca's default theme - Light colors",
    "url": "https://github.com/RichardDern/cyca_theme_light",
    "screenshot": "images/screenshot.png",
    "icons": null,
    "inherits": "cyca-dark"
}

Note that icons is set to null, because we won’t provide any icons in that theme: icons will be inherited from the cyca-dark theme, which we explicitely mention with the inherits key.

Our theme.js file got considerably lighter:

const _ = require('lodash');

const theme = _.merge(require("../cyca_theme_dark/theme"), {
    theme: {
        colors: require("./colors")
    }
});

module.exports = theme;

The colorScheme.js file still references all the colors used in the theme, which, obviously, has changed from the cyca-dark theme:

module.exports = {
    white: "#ffffff",
    black: "#000000",
    red: {
        "100": "#FFF5F5",
        "800": "#9B2C2C",
        "900": "#742A2A"
    },
    yellow: {
        "100": "#FFFAF0",
        "500": "#ED8936"
    },
    green: {
        "100": "#F0FFF4",
        "400": "#68D391",
        "600": "#38A169",
        "800": "#276749",
        "900": "#22543D"
    },
    blue: {
        "400": "#63B3ED",
        "500": "#4299E1",
        "700": "#2B6CB0",
        "800": "#2C5282"
    },
    purple: {
        "500": "#9F7AEA"
    },
    "cool-gray": {
        "700": "#4A5568"
    },
    gray: {
        "100": "#F3F3F3",
        "200": "#E8E8E8",
        "300": "#DCDCDC",
        "400": "#D0D0D0",
        "500": "#C5C5C5",
        "600": "#B9B9B9",
        "700": "#ADADAD",
        "800": "#A2A2A2",
        "900": "#969696"
    }
}

But the colors.js file also got lighter:

const baseColors = require('./colorScheme');

module.exports = {
    /**
     * Base
     */
    a: {
        normal: baseColors.blue[800],
        hover: baseColors.blue[700]
    },
    article: {
        text: baseColors.black,
        bg: baseColors.gray[500],
        border: baseColors.gray[500],
        body: baseColors.black
    },
    body: {
        text: baseColors.black,
        bg: baseColors.gray[100]
    },
    label: {
        text: baseColors.gray[900]
    },
    input: {
        text: baseColors.black,
        bg: baseColors.white
    },
    scrollbar: baseColors.gray[900],
    /**
     * Components
     */
    badge: {
        text: baseColors.black,
        bg: baseColors.gray[700],
        "bg-article": baseColors.gray[700]
    },
    caret: baseColors.gray[900],
    "folders-tree": baseColors.gray[500],
    "documents-list": baseColors.gray[400],
    "feeditems-list": baseColors.gray[300],
    "details-view": baseColors.gray[200],
    "list-item": {
        text: baseColors.black,
        active: {
            text: baseColors.black,
            bg: baseColors.gray[700]
        },
    },
    "feed-item": {
        text: baseColors.black,
        active: {
            text: baseColors.black,
            bg: baseColors.gray[500]
        },
        read: baseColors.gray[900],
        meta: baseColors["cool-gray"][700]
    },
    account: {
        menu: {
            bg: baseColors.gray[300],
            item: {
                text: baseColors.gray[900],
                selected: {
                    text: baseColors.black
                }
            }
        },
        content: {
            bg: baseColors.gray[200]
        }
    },
    alerts: {
        success: {
            bg: baseColors.green[100],
            text: baseColors.green[900]
        },
        error: {
            bg: baseColors.red[100],
            text: baseColors.red[900]
        },
        warning: {
            bg: baseColors.yellow[100],
            text: baseColors.yellow[500]
        }
    }
};

Note that compared to cyca-dark theme’s colors.js file, this is smaller, because we inherited some components colors, so we don’t need to re-declare them unless we changed it in the new theme.

As your theme inherits from another one, you need to install parent’s theme as well, because compilation process requires files from it.

You will need to know parent theme’s repository URL. In the light theme example, we need the cyca-dark theme’s repository URL, which is https://github.com/RichardDern/cyca_theme_dark.

Please note that if parent theme itself inherits from another theme, you will need to install that theme too.

To install a theme and once you know it’s repository URL, you can use the following command, while in Cyca’s root directory:

cd resources/themes/
git submodules add https://github.com/RichardDern/cyca_theme_dark

As for now, this procedure can be quite difficult, but I am working on making it a lot simpler, especially regarding dependencies with other themes.

Compiling your theme

You can use Laravel’s default assets compilation pipeline to make your development easier.

For instance, you can use the following command:

npm run watch

Which will compile resources as soon as you saved them.

Once you are satisfied with your work, you will want to compile your theme for production, which is done easily by using the following command:

npm run prod

This command will create a dist/ directory in your theme’s structure. It is important you provide this directory in your theme’s repository, because it is the one Cyca will use when someone will install your theme.

You can look at Cyca’s official themes repositories to see how yours should look like:

Publishing your theme

To make your theme available to Cyca’s users, you need at least a git repository (GitHub or self-hosted, it doesn’t matter as long as your repository has the aformentioned structure). Then you need to make a pull request to Cyca’s themes database repository.

You need to add your theme in the following form:

{
    "community": {
        "your-theme-name": "<your git repository URL>"
    }
}

Your theme will be examinated, then, in the best case, included to Cyca’s themes database.