Skip to content

[CSS] Introduction to campaign CSS

Salvatos edited this page Jun 1, 2023 · 5 revisions

Campaign CSS is a set of CSS rules (Wikipedia) that allows you to customize the appearance of your campaign for every visitor. If you have no idea what CSS itself is, I recommend familiarizing yourself with it first, since this guide is more about how to use CSS on Kanka, and not about teaching CSS itself. The W3C tutorial can be a good place to start. Follow the links in the article to learn more about the various concepts you aren’t already familiar with.

To edit your campaign's CSS, click the Campaign or World links in the main sidebar to access your campaign’s main page. There, select Theming in the menu and create a new style. You can create up to 30 separate stylesheets to compartmentalize your CSS rules into logical groups, making them easier to locate and edit later on.

CSS priority

One thing to keep in mind is that in CSS, when conflicting rules are found, there are two ways the browser chooses which one to apply. The most important one is specificity: the more specific a rule, the more importance it is given. This W3C page explains it simply while detailing the "weights" of various types of declaration, so I won’t rehash it here.

This prioritization is especially important when you attempt to style elements whose appearance is already set explicitly by Kanka's code, or by Marketplace themes you've installed. If Kanka has a built-in rule for p {}, you will need a rule that is at least equally specific to override it (for example, p {} or body p {}).

The second way CSS rules are prioritized is by order of appearance: if two rules are equally specific, the browser will apply the last one it finds, assuming that it is meant to override an earlier declaration. If you have a CSS file that looks like p { color: black; } p { color: red; }, your paragraph's text will be red. We need to be aware of this because Kanka uses multiple stylesheets that it loads in a specific order, chosen specifically to leverage this ordering logic. When it comes to customizing your campaign, the cascading order you need to keep in mind is as follows:

  1. Kanka's generic stylesheets (from frameworks, libraries and Kanka itself)
  2. Content-specific stylesheets (such as dashboard.css for the Dashboard or the Leaflet stylesheet for the maps module)
  3. The active theme's stylesheet, if other than Default
  4. campaign_plugin.styles for Marketplace themes
  5. campaign.styles for campaign CSS
  6. High-priority styles like Font Awesome

As you can see, this means that, at equal specificity, 1) Marketplace themes override Kanka themes, and 2) your campaign's CSS overrides both. This is great because it means you can build a community theme on top of a specific Kanka theme, and you can install Marketplace plugins to do most of the work for you but still tweak some rules to your liking. It's also a reminder that your customizations may look different on certain pages such as the Dashboard because specific stylesheets are being loaded that aren't used elsewhere.

It's worth noting that you can cheat with priority by marking a rule as important, like so: p { color: black !important; }. This will supersede the priority check, but it is considered bad practice and should only be used as a last resort (for example if you need to override inline styling on an element, and you can’t make a declaration specific enough to take precedence over inline styling). If you end up having multiple conflicting !important rules (a sign that things are really getting out of hand), specificity and order of appearance will once again be called into action to choose among them.

Screen size

As is common practice, Kanka applies different CSS rules based on the user's screen size. While that doesn't matter to you if you're only changing the color of an element, it can lead to undesirable results when you change the sizing or layout of entire blocks. Fortunately, Kanka keeps things simple most of the time with two main formats: 767px-wide or less, and 768px-wide or more. When you need to make changes that should only affect one of those views, wrap your declarations within a @media rule as appropriate:

/* Change background color on small screens */
@media (max-width:767px) {
  .wrapper {
    background-color: blue;
  }
}
/* Change background color on large screens */
@media (min-width:768px) {
  .wrapper {
    background-color: blue;
  }
}

Tip: Indenting your code will help you remember to close that extra set of brackets.

One thing that can lead to unexpected behaviour is that Kanka also uses JavaScript to alter elements based on screen size or user interactions, so you may need to inspect elements in various states to find out how exactly they can change dynamically and account for those changes. One example is the sidebar-collapse class, detailed below (Sidebar classes).

Practical tips

Dev tools

Now is a good time to introduce you to your browser’s developer tools, which are extremely helpful when working with CSS. These tools allow you to inspect elements on the page to see their classes and ID, see exactly how the HTML is structured around them, what CSS rules apply to them (including ones that are overridden by specificity or sequence) and what files those rules come from. They also include extremely convenient features that allow you to fake states such as hovered or active, light/dark theme user preferences, printing mode or mobile device resolutions. Last but definitely not least, you can edit, disable and write additional CSS rules or entire stylesheets directly on your live page to test changes without going back and forth editing and saving your campaign styles and refreshing a page to see the results.

Chrome and Firefox use essentially identical dev tools, so here is a link to Chrome’s exhaustive guide to dev tools. Other popular browsers are likely to have similar features available.

Query parameters

There are a few parameters you can add to Kanka URLs (a part of the URL called query string) to turn on or off certain styles and speed up your testing and exploration. First, note a couple rules that apply to all URL query strings:

  1. The first parameter is always preceded by a question mark: .../overview?param=value.
  2. Additional parameters are always preceded by an ampersand: .../overview?param1=value&param2=value.
  3. Therefore, whenever someone refers to query parameters, whether they mention &param or ?param or just param, you should prefix them with the appropriate character based on your specific URL.

?_theme: Loads the page in a specific theme regardless of user or campaign preferences. Possible values are "base", "dark" and "midnight".

?_plugins=0: Loads the page without the campaign_plugin.styles stylesheet, disabling all Marketplace themes.

?_styles=0: Loads the page without the campaign.styles stylesheet, disabling all campaign styles.

Themes and custom properties

Kanka proposes a few core themes (Base, Dark and Midnight Blue) out of the box. Boosted or Premium campaigns can force one of these themes for all visitors, but you can also let Kanka use whichever theme a user has selected in their profile (or Base by default for logged out visitors). If you go for the latter option, it may be tricky to set certain colors in your campaign CSS, since they might not suit the active theme. The solution? CSS custom properties.

Those properties are like variables set by each Kanka theme and which you can refer to in your campaign CSS instead of imposing a preset value. For example, the official Dark theme sets --theme-main-text: #A4A4A4;. If you want to make a given element that normally uses a different color match the active theme's main text color, you can therefore use color: var(--theme-main-text); on that element instead of giving it a specific color that might not look right in other themes.

Similarly, your campaign CSS can override these custom properties' values, to more easily customize official themes and those from the Marketplace. You might do so globally at the :root level or, for example, in entities of a specific type for use in multiple parts of your theme, but not everywhere:

:root {
  --theme-main-text: darkblue;
}
body.kanka-entity-character {
  --theme-main-text: green;
}

This allows you to change only the variable's value and instantly affect every element that refers to it, instead of identifying and targeting all such elements individually.

To get the most up-to-date list of custom properties, I suggest switching to the Dark theme on your campaign and inspecting the dark.css stylesheet. Pro tip: you can add ?_theme=dark to any Kanka URL to view the current page in a different theme without changing your user or campaign preferences!

Context-specific classes

Kanka probably uses hundreds of CSS classes, so we won't go over all of them here, but there are a few important ones that you will likely want to make use of. The following are classes that change depending on the content you are viewing, or the state of certain elements on the page, and can help you style things conditionally.

Entity classes

Several very useful classes can be present on the body element:

Entity type: Indicates the type of entity being viewed, for example kanka-entity-journal for a Journal. Similarly, the main entity lists each have a similar class such as kanka-abilities or kanka-characters.

Custom type: Indicates the type assigned to an entity, for example kanka-type-npc on a Character given the "NPC" type. Note that it is all lowercase and spaces are replaced with hyphens.

Entity id: Indicates the unique ID of a specific entity, for example kanka-entity-1.

Tags: An entity's tags are identified in two formats, namely their unique IDs and their names in "slug" form, for example kanka-tag-245634 kanka-tag-classabilities for a tag named "Class Abilities". The surest way to have the right spelling of those slugs is to inspect the body of a relevant entity.

entity-story: Indicates that the current page is the main Overview page of an entity, as opposed to its secondary pages or other parts of Kanka. Most subpages also have a similar class to identify them — entity-attributes, entity-abilities, entity-relations and so on.

Some examples of custom selectors

Those classes allow you to define rules that apply to all entities of a given type, or to customize a unique entity very specifically. The easiest way to ascertain the name of the class you are looking for is to look at the page's source code in your browser and check the body tag's classes. Below are a few examples of custom selectors you could make for various scenarios. I will use two of my own test entities to demonstrate, with the following body tags:

<!-- Overview page of a Timeline with two tags -->
<body class="kanka-entity-1955453 kanka-entity-timeline kanka-tag-166712 kanka-tag-horizontaltimeline kanka-tag-91036 kanka-tag-test  entity-story" >
<!-- Attributes page of an Ability with one tag -->
<body class="kanka-entity-1202673 kanka-entity-ability kanka-tag-91036 kanka-tag-testtemplate  entity-attributes" >

Target a specific entity by ID:

.kanka-entity-1202673 { ... }

Target all entities with a certain tag (by tag slug):

.kanka-tag-horizontaltimeline { ... }

Target only the Attributes subpage of all entities with a certain tag slug:

.kanka-tag-test.entity-attributes { ... }

Target all entities of a certain type (Ability) that don’t have a certain tag slug:

.kanka-entity-ability:not(.kanka-tag-test) { ... }

Target a few types of entities that match a certain tag slug, except one specific entity:

.kanka-tag-test:is(.kanka-entity-ability, .kanka-entity-note, .kanka-entity-creature):not(.kanka-entity-1202673) { ... }

Target all entities with any tag that starts with "test" (for example Test and Test Template), excluding their Connections page:

body[class*="kanka-tag-test"]:not(.entity-relations) { ... }

Styling posts

Since Kanka 1.39.2, you can use the CSS class field on individual posts to effectively tag them as targets for custom CSS. For example, you could use this to give a standard style to a "Heraldry" post that you add to various organisations and families, without affecting other posts or entries. Your space-delimited classes will be added to the outermost container of the post, so you could target all of those posts regardless of which entity they appear on with something like .entity-posts .my-css-class {...}.

Mention attributes

Similarly, entity mention links include various HTML attributes that can be used in CSS selectors to style them based on what they link to. All of them also have the entity-mention class.

data-id: Indicates the unique ID of the target entity. Example: data-id="12345", which could be targeted with .entity-mention[data-id="12345"] {}.

data-entity-tags: Contains the ID and slug of each tag applied to the mentioned entity. Note that these slugs don’t necessarily use the same replacement logic for spaces and special characters as the tags in the body’s tag classes, so it’s best to inspect a mention to find out the correct slug. Example: data-entity-tags="id-41414 sandwich-recipes", which could be targeted with either of:

  • .entity-mention[data-entity-tags~="id-41414"] {} (inclusive word match ~=: this specific tag ID — and not "id-414141", for instance)
  • .entity-mention[data-entity-tags*="recipes"] {} (partial match *=: any tag that contains the word "recipes", including "badrecipes" and "goodrecipes")
  • .entity-mention[data-entity-tags="id-41414 sandwich-recipes"] {} (exact match =: this specific tag and no other)
  • etc.

For more details on attribute selector syntax, see MDN.

Note that other elements in Kanka may be similarly identified, such as individual abilities in the Abilities subpage of an entity, allowing you to use tags to apply different styles to different categories of content of a similar nature. More of these are added over time as requested, so use the browser’s dev tools to inspect the element you want to style and see if its tags are identified, and otherwise feel free to submit a feature request to have them added.

Sidebar classes

Sidebar state: When the sidebar is closed, the class sidebar-collapse is applied to the body tag. When it is open, no class is applied (so you can use body:not(.sidebar-collapse) {} to target that state).

User privileges: A class exists on the element aside.main-sidebar that indicates whether the user is a logged-in member of the campaign: main-sidebar-member or main-sidebar-public. This allows you to hide or show certain elements accordingly, for example:

/* to hide sidebar elements from public viewers (for example, quick links that are only useful to your players) */
.main-sidebar-public .sidebar-quick-link-1 {
  display: none;
}
/* to hide or alter other interface elements that aren't as useful if someone isn't logged in, such as entity sidebars */
.main-sidebar-public ~ .content-wrapper .entity-menu-2 {
  display: none;
}
/* conversely, to add or restyle content for your members' eyes only */
.main-sidebar-member ~ #campaign-dashboard .campaign-title::after {
  content: "– the best campaign ever!";
}

Other useful classes and attributes

  • entity-content is used in all places that display the entry or post content of an entity, such as: the entity’s Overview page, entity preview widgets on the dashboard, map sidebars in Explore mode, timeline eras and elements, etc. It is very useful for consistently styling basic elements such as paragraphs and lists in user-generated content across all of Kanka without affecting the general interface.
  • is-admin is added to the body tag when the active user is the campaign’s administrator. You could use this to hide content from most users but keep it visible to yourself, or simply use it as a way to style things differently for yourself only. Just keep in mind that a nosy enough person could add the class to a live page manually, and that content hidden with CSS is still "physically" present on the web page, just not displayed until instructed otherwise.
  • data-user-member is an HTML attribute on the body tag that indicates whether the user is a logged-in member of the campaign, so you could target it with body[data-user-member="1"] {} or body[data-user-member="0"] {}.

Using custom fonts

A popular way to customize campaigns is to use different fonts than the Kanka defaults. There are two ways to do so: uploading them to your campaign Gallery and defining them using CSS @font-face rules, or importing them from services such as Google Fonts using CSS @import rules.

@font-face

  1. First, you will need to use a WOFF2 file as that is the only font format currently allowed in the Gallery. There are various websites that can convert font files for you if yours is in a different format. During this step, you should also ensure that your font’s licensing allows you to use it on a public website and check whether it requires attribution.
  2. Upload your font file to the Gallery, then click its thumbnail to view its details. Near the bottom, you will see a link to the file, whose URL you need to copy (it will be a very long amazonaws.com URL).
  3. In any of your campaign’s styles (accessible via World > Theming), add a @font-face rule pointing to that URL and give it any name you want. You can then use that font anywhere in your campaign’s styles with the font-family property (no need to define it in every style!).
@font-face {
	font-family: ILikeThisName;
	src: url("nobody-has-time-for-long-urls.woff2");
}

.this-class-is-special {
	font-family: 'ILikeThisName', serif;
}

@import

This procedure is explained by the Kanka documentation and this tutorial video. However, always keep in mind that @import rules only work when they are placed at the top of a stylesheet (comments notwithstanding).

Valid ✅ Valid ✅ Invalid ❌
@import url('...');
body { ... }
/* Any number of comments */
@import url('...');
body { ... }
body { ... }
@import url('...');

Since all the styles you create in the Theming page are merged into a single stylesheet, I recommend using a style specifically for fonts and ensuring that it's always first in the list. Unfortunately, new styles are placed at the top by default, so you will need to remember to reorder them every time you add one.


If this guide helped you, a tip goes a long way to keep me making more of this kind of content =) I am also sometimes available for commissions to help directly with your templates or CSS. You can find me on Ko-fi: