Dashboard customization

This article mainly describes the HTML structure of Kanka dashboards and offers some guidance and tips about customizing it. Since most widget types have small differences in their markup, it can also help you plan ahead without having to create demos for each type and examine them on a live page.

Layout

Before we dive into the various widget types, it’s important to know how the dashboard is laid out. It consists of two distinct areas: the campaign header at the top (div.campaign-header) and the content area (section.content), the latter containing all dashboard widgets and a final div for the Dashboard Settings button. Here is a (simplified) general overview of the page’s HTML structure:

<div id="campaign-dashboard" class="content-wrapper">
  <!-- Campaign header begins -->
  <div class="campaign-header campaign-imaged-header ...">
    <!-- See Campaign Header section below for full markup -->
  </div>
  <!-- Widgets section begins -->
  <section class="content">
    <div class="max-w-7xl mx-auto">
      <div class="dashboard-widgets grid grid-cols-12 ...">
        <!-- Widgets; details below -->
        ...
      </div>
      <!-- Extra row for Settings button -->
      <div class="text-center mt-6">
        <a href="https://app.kanka.io/w/1/dashboard-setup" class="btn2 btn-lg btn-primary" title="Dashboard Setup">
          <i class="fa-solid fa-cog " aria-hidden="true"></i>
          Dashboard Setup
        </a>
      </div>
    </div>
  </section>
</div>

Campaign Header

The campaign header is mandatory on the default dashboard, and optional on additional dashboards. Its various containers control the background image for the whole section, the background for the campaign presentation, the campaign’s title and the introduction text set in the campaign’s settings under the Dashboard tab. On someone else’s campaign, div.action-bar contains a link to follow/unfollow the campaign (so it's a good idea to test your campaign in an incognito window or while impersonating another user if you make heavy changes in this area). Users with sufficient access rights also see a dropdown menu there that leads to other dashboards and various options.

  <div class="campaign-header campaign-imaged-header ..." style="background-image: url(https://th.kanka.io/.../image.png)">
    <div class="campaign-header-content ...">
      <div class="campaign-content">
        <div class="campaign-head ...">
          <div class="grow">
            <a href="https://app.kanka.io/w/1/overview" title="Campaign Name" class="campaign-title ...">Campaign Name</a>
          </div>
          <div class="action-bar ...">
            <!-- Follow button, if not looking at your own campaign -->
            <button id="campaign-follow" ...>
              <i class="fa-solid fa-star"></i>
              <span id="campaign-follow-text">Stop following</span>
            </button>
            <!-- Otherwise, action dropdown -->
            <div class="dropdown">
              <button class="btn2 btn-sm" data-dropdown="" aria-expanded="false" data-loaded="1">
                <i class="fa-solid fa-ellipsis-h " aria-hidden="true"></i>
              </button>
              <div class="dropdown-menu hidden" role="menu">
                <!-- Dropdown links to other dashboards and various settings -->
              </div>
            </ul>
          </div>
        </div>
        <div class="preview">
          <!-- Contains the excerpt set in the campaign’s main settings, Dashboard tab -->
        </div>
      </div>
    </div>
  </div>

Even on non-Premium campaigns, a header image can be set as a backdrop to this entire area. If set, div.campaign-header gains the .campaign-imaged-header class, which means you can not only control the background’s display, but also style any child element of div.campaign-header based on the presence or absence of a cover image. For example, you could control the padding of div.preview based on the presence or absence of an image using .campaign-imaged-header .preview {...} and .campaign-header:not(.campaign-imaged-header) .preview {...} respectively.

Widgets

Widgets come in a variety of types and sizes, each with dedicated classes that allow us to create specific rules based on both their content and their dimensions. You can also give individual widgets custom CSS classes in their Advanced tab, which can greatly simplify your selectors compared to using the size- and type-related classes as described below. For example, you could have a "blue-widget" class that gives those widgets a blue background. However, since some widget types have different HTML structures (detailed below), not all customizations will work across multiple types without some effort.

Note however that those custom classes aren't applied to the outermost layer of a widget, but to the second layer (referred to as "secondary container" in the widget type table below). In most cases, that shouldn't hinder styling since all content is under the secondary layer, but if you aim to make more drastic layout changes that require grabbing the whole widget, you may need to use the :has() pseudo-class, for example div.widget:has(.blue-widget) {...}.

Widget size and layout

On the dashboard, widgets are laid out using a CSS grid, which gives us a lot of flexibility and control. When set up, each row can contain from 1 to 4 widgets depending on their size, and their positions are set via drag-and-drop. To accommodate the various possible widths, each row of widgets is broken down into 12 virtual columns "under the hood", and widgets occupy a certain number of those columns based on their defined width.

For example, a 100%-width widget occupies all 12 columns via the class div.md:col-span-12, while a 33%-width widget occupies 4 columns via div.md:col-span-4. Therefore, you can target all widgets of a given format with a rule such as .md\:col-span-12 > .widget {...}.

Colons in class names are an abomination (yes, I know, they are valid HTML) that requires "escaping" with a backslash character in CSS selectors. .md:col-span-12 { ... } is not valid CSS since colons indicate pseudo-classes and there is no "col-span-12" pseudo-class. Thanks, Tailwind 😤

The following table shows each available widget size by name, percentage and class.

Format name
Tiny
Small
Half
Wide
Large
Full

Width percentage

25%

33%

50%

66%

75%

100%

Column class

col-span-3

col-span-4

col-span-6

col-span-8

col-span-9

col-span-12

Widget types

All widgets have the div.widget class and an additional class based on their type, plus a unique ID in the form of widget-col-[id]. The next layer specifies the widget’s subtype, with a matching ID in the form of dashboard-widget-[id]. Maps are differentiated on a third layer.

Type and subtype
Primary container
>
Secondary container
>
Tertiary container

Entity preview

div.widget.widget-preview

>

div.widget-preview

Entity preview (map)

div.widget.widget-preview

>

div.widget-preview

>

div.widget-map

Random entity preview

div.widget.widget-random

>

div.widget-random

Calendar

div.widget.widget-calendar

>

div.widget-calendar

Entity list (such as unmentioned or recently modified)

div.widget.widget-recent

>

div.widget-list

Entity list (single entity preview)

div.widget.widget-recent

>

div.widget-preview

Text header*

div.widget.widget-header

*Heads up: .widget-header is used both to identify Header-type widgets and on the header part of all widgets that have a header and a body. Therefore, .widget.widget-header {...} (with chained classes) and .widget .widget-header {...} (with space-separated classes) are deceptively different selectors: the first targets Header widgets’ outer container, while the second targets the header inside most widgets.

These classes give us the flexibility to precisely target any combination of types and formats, as well as specific widgets by ID or arbitrary groups of them (via custom classes). Because .widget-preview is so overused and the same classes can appear on two different levels, however, we need to be fairly specific with our selectors:

/* Full-width entity previews, excluding maps: */
.md\:col-span-12 > .widget-preview:not(:has(.widget-map)) {...}

/* Calendars that are at least 50% width: */
:is(md\:col-span-12, md\:col-span-9, md\:col-span-8, md\:col-span-6) > .widget-calendar {...}

/* All widgets with our custom class except a specific one, once we have determined its ID: */
.custom-class-name:not(#dashboard-widget-1234) {...}
/* Note that custom classes are applied to the secondary container, NOT to the top-level div.widget, so it may not be suitable for resizing and other effects targeting the outermost container. For those, you can use the #widget-col-1234 ID. */

Widget structure

All widgets except text headers, map previews and attribute previews use a similar content structure: a div#widget-col-(id).widget.col-md-(1-12) specifying the width and general type of widget, a div#dashboard-widget-(id) indicating the more specific subtype, and some variation of a header and a body. The header and body are structured according to a few different models which are detailed below.

List header

The simplest header is used on list widgets such as Unmentioned entities and Recently modified entities. It simply consists of an h4.text-lg with two spans:

<!-- List widget - no link -->
<h4 class="text-lg mb-3 px-4 pt-4 flex gap-2">
  <span class="grow">
    Recently modified
  </span>
  <span class="flex-none flex gap-1"></span>
</h4>

However, when using a Recently modified list for a single entity type, the header will have a link instead of plain text, which may lead to inconsistent styling between lists if not accounted for:

<!-- List widget - link to specific entity type -->
<h4 class="text-lg mb-3 px-4 pt-4 flex gap-2">
  <span class="grow">
    <a href="https://app.kanka.io/w/1234/events">Events - Entity list</a>
  </span>
  <span class="flex-none flex gap-1"></span>
</h4>

Also quite simple, entity previews have a div.header with a link. Private entities and dead characters have an extra span to denote their status with an icon:

<!-- Entity preview - link & status icons -->
<div class="widget-header">
  <!-- This span is only present if the entity is private -->
  <span data-title="This entity is private and only visible to members of the campaign's Admin role." data-toggle="tooltip" data-html="true"><i class="fa-solid fa-lock " aria-hidden="true"></i></span>
  <!-- This is the actual link and title -->
  <a href="https://app.kanka.io/w/19153/entities/493914" class="flex gap-1 text-xl p-4 pb-0">
    <span class="grow">Dead Character</span>
    <!-- This span is only present if a dead character icon is needed -->
    <span data-title="Dead" data-toggle="tooltip" data-html="true"><i class="ra ra-skull " aria-hidden="true"></i></span>
  </a>
</div>

Entity image header

On entity previews that are set to use the entity’s image or header image (and where such image exists), the top structure is slightly different. The image is shown first separately, and the title underneath; both of them link to the entity:

<div class="widget-header">
  <a href="https://app.kanka.io/w/.../entities/..." class="widget-image cover-background bg-center aspect-video rounded-t ">
    <picture class="entity-image-wide">
      <source srcset="https://th.kanka.io/....jpg" media="(min-width: 768px)">
      <img src="https://th.kanka.io/....jpg" class="w-full">
    </picture>
  </a>
  <a href="https://app.kanka.io/w/.../entities/..." class="flex gap-1 text-xl p-4 pb-0">
    <span class="grow">Entity Name</span>
  </a>
</div>

At the time of writing, the class .entity-image-wide on the picture element isn't actually used in Kanka's CSS, so most of your image styling would override .widget-image and other classes on the anchor instead.

No header

On map entity previews, there is no header at all, but it is possible to fake one with CSS (example below). There is however an extra layer before the actual content, with div.widget-map identifying the entity as a map.

Entity preview body (full)

When an entity preview is set to display its full entry in the Setup tab, .widget-body is at its most simple and contains only the entity’s content:

<div class="widget-body p-4">
  <div class="entity-content">
    ...
  </div>
</div>

Entity preview body (default)

By default, only part of the entry is visible, up to about 200 pixels in height, with a toggle at the bottom of the widget to expand it and show the full entry. .widget-body’s first child gains a specific ID for toggling purposes: #widget-preview-body-(id). This div contains div.entity-content and a second div with a gradient effect when collapsed. It is followed by the anchor that toggles expansion: a.preview-switch.

<div class="widget-body p-4 ">
  <!-- max-h-52 is removed when the widget is expanded: -->
  <div class="preview overflow-hidden relative max-h-52" data-toggle="preview" id="widget-preview-body-1234">
    <div class="entity-content">
      ...
    </div>
    <!-- This gradient div gains 'display:none' when the widget is expanded: -->
    <div class="absolute w-full bottom-0 h-52 gradient-to-base-100">
    </div>
  </div>
  <a href="#" class="preview-switch inline-block w-full text-center" id="widget-preview-switch-1234" data-widget="1234" data-toggle="tooltip" data-title="Click to toggle">
    <i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
    <span class="sr-only">Click to toggle</span>
  </a>
</div>

Entity preview body (with pinned relations, attributes or group members)

The Advanced tab of widget settings allows you to include pinned relations or attributes in the preview, and Family or Organization members. Doing so adds corresponding divs under the entity content, which allows you to style and position these blocks creatively independently from the rest of the content (see below for an example):

<div class="entity-content">
  ...
</div>
<!-- Optional Relations block -->
<div class="widget-advanced-relations">
  <dl class="dl-horizontal">
    <div class="pinned-relation flex gap-2 flex-wrap" data-target="1234" data-relation="Pionniers arcadiens" data-visibility="1" data-attitude="">
      <strong class="">Relation name</strong>
      <span class="grow text-right">
        <span>
          <a class="name" data-toggle="tooltip-ajax" data-id="1234" data-url="https://app.kanka.io/w/.../tooltip" href="https://app.kanka.io/w/..." data-loaded="1" aria-expanded="false">Relation target</a>
        </span>
      </span>
    </div>
    <!-- repeat div.pinned-relation, optionally with .relation-repeat added for identical relation types (e.g. "sibling") ... -->
  </dl>
</div>

<!-- Optional Attributes block -->
<div class="widget-advanced-attributes">
  <ul class="m-0 p-0 list-none">
    <div class="pinned-attribute flex gap-2 flex-wrap " data-attribute="Name" data-target="1234">
      <strong>Attribute name</strong>
      <p class="text-right grow m-0 inline-block">Attribute value</p>
    </div>
    <!-- repeat div.pinned-attribute... -->
  </ul>
</div>

<!-- Optional group members block -->
<div class="widget-advanced-members">
  <div class="flex flex-col gap-2 members">
    <div class="grid grid-cols-2 gap-2 members" data-role="role" data-status="1">
      <div class="font-extrabold">Member role</div>
      <div>
        <span>
          <a class="name" data-toggle="tooltip-ajax" data-id="1234" data-url="https://app.kanka.io/w/.../tooltip" href="https://app.kanka.io/w/..." data-loaded="1" aria-expanded="false">Member name</a>
        </span>
      </div>
    </div>
    <!-- repeat div.members ... -->
  </div>
</div>
<div class="absolute w-full bottom-0 h-52 gradient-to-base-100">
</div>

List body (such as Recently modified and Unmentioned entities)

List widgets, unless set to show only the first result’s entity preview, have the .widget-list class on the second level. This in turns contains a simple h4 header and a div.widget-recent-list where the actual results appear. Up to ten results are shown in a single column (div.flex-col), each containing elements for the entity’s image, its name and a div.blame for the name of the last editor and the timestamp. Additionally, a link in div.text-center > a.widget-recent-more loads the next set of results, where applicable.

<div class="widget-recent-list overflow-auto px-4 pb-4 max-h-[400px]">
  <div class="flex flex-col gap-2">
    <div class="flex items-center gap-2">
      <!-- Entity thumbnail as a clickable background image -->
      <a class="entity-picture inline-block rounded-full cover-background w-9 h-9 flex-shrink-0" style="background-image: url('https://....cloudfront.net/images/defaults/journals_thumb.jpg');" title="Entity Title" href="https://app.kanka.io/w/..."></a>
      <div class="grow break-all">
        <span>
          <a class="name" data-toggle="tooltip-ajax" data-id="1234" data-url="https://app.kanka.io/w/.../tooltip" href="https://app.kanka.io/w/..." data-loaded="1" aria-expanded="false">Entity Title</a>
        </span>
        <!-- If private: -->
        <i class="fa-solid fa-lock" title="This entity is private and only visible to members of the campaign's Admin role." aria-hidden="true"></i>
        <span class="sr-only">This entity is private and only visible to members of the campaign's Admin role.</span>
      </div>
      <div class="blame flex-none text-right text-xs">
        <span class="author block">Author’s name</span>
        <span class="elapsed text-neutral-content text-xs" title="2023-06-16 03:20:18 UTC">7 months ago</span>
      </div>
    </div>
    <!-- Repeat up to 10 times -->
    <div class="text-center">
      <a href="#" class="widget-recent-more" data-url="https://app.kanka.io/w/.../dashboard/widgets/recent/...?page=2">
        <span class="inline-block p-3">Next</span>
        <!-- Spinner animation while loading results; display: none afterwards -->
        <i class="fa-solid fa-spinner fa-spin spinner" style="display: none;" aria-hidden="true"></i>
      </a>
    </div>
  </div>
  <!-- Additional results are appended here in a new .flex-col -->
</div>

Note that when you load additional results, they are added after the current "Next" link with a new "Next"of their own. However, the old "Next" div still exists and is simply emptied. Therefore, even though it is normally invisible due to being empty, you may see undesirable effects between sets of results if you style that div rather than the link directly. This can be avoided using the :has pseudo-class to check whether a link is present:

.widget-recent-list .text-center:has(a) {
	border: 1px solid blue;
}

Conversely, you could make use of that distinction to add visual separation between batches of results without affecting the current link, with a rule such as:

.widget-recent-list .text-center:not(:has(a)) {
	border-top: 1px dashed slategrey;
	margin-bottom: 5px;
}

Calendar body

For calendars, the header is followed by a non-specific div.p-4, which itself contains a div.widget-loading for a spinning animation, followed by div#widget-body-(id) for the actual content. Let’s look at the subheader at the top first, showing the current date with back and forward buttons:

<!-- While loading, show a spinner, then display: none -->
<div class="text-center py-10 text-2xl" id="widget-loading-12345" style="display: none;">
  <i class="fa-solid fa-spinner fa-spin " aria-hidden="true"></i>
</div>
<div id="widget-body-12345">
  <div class=" grid gap-4 w-full grid-cols-1 md:grid-cols-2 ">
    <!-- Subheader with current day and back/forward buttons-->
    <div class="col-span-2 current-date text-center text-xl flex items-center justify-center gap-2" id="widget-date-12345">
      <a href="#" class="widget-calendar-switch" data-url="https://app.kanka.io/w/1234/dashboard/widgets/calendar/12345/sub" data-widget="12345" data-toggle="tooltip" data-title="Change date to previous day" role="button">
        <i class="fa-solid fa-chevron-circle-left" aria-hidden="true"></i>
        <span class="sr-only">Change date to previous day</span>
      </a>
      <span>3 February, 9001 </span>
      <a href="#" class="widget-calendar-switch" data-url="https://app.kanka.io/w/1234/dashboard/widgets/calendar/12345/add" data-widget="12345" data-toggle="tooltip" data-title="Change date to next day" role="button">
        <i class="fa-solid fa-chevron-circle-right" aria-hidden="true"></i>
        <span class="sr-only">Change date to next day</span>
      </a>
    </div>
    ...

.current-date is a good target for decorating this section as a whole, whereas .current-date > span would allow you to style the date itself without affecting the screen reader hints (.sr-only).

This is followed by two columns of up to 5 past and 5 upcoming events:

    ...
    <!-- Previous events column -->
    <div class="flex flex-col gap-2 ">
      <div class="text-lg">
        Previous
        <a href="//docs.kanka.io/en/latest/guides/dashboard.html#known-limitations" target="_blank" data-toggle="tooltip" data-title="Why are these reminders being shown?">
          <i class="fa-solid fa-question-circle " aria-hidden="true"></i>
          <span class="sr-only">Why are these reminders being shown?</span>
        </a>
      </div>
      <ul class="style-none p-0">
        <li data-ago="41" class="flex gap-2">
          <div class="grow">
            <a href="https://app.kanka.io/w/1234/entities/2976043">Event Name I</a>
          </div>
          <div class="flex gap-1 items-center">
            <!-- If the reminder has a Comment filled in, an icon can be hovered to read it: -->
            <i class="fa-solid fa-comment" data-title="Reminder comment is feeling talkative" data-toggle="tooltip" data-placement="bottom" aria-hidden="true"></i>
            <!-- Hovering the calendar icon shows the event’s date: -->
            <i class="fa-solid fa-calendar" data-title="18 May, 9000 " data-toggle="tooltip" data-placement="bottom" aria-hidden="true"></i>
          </div>
        </li>
          <div class="grow">
            <a href="https://app.kanka.io/w/1234/entities/3313789">Event Name II</a>
          </div>
          <div class="flex gap-1 items-center">
             <!-- If the reminder is a recurring event, a circular arrow icon indicates it: -->
            <i class="fa-solid fa-arrows-rotate" data-title="Recurring" data-toggle="tooltip" aria-hidden="true"></i>
            <i class="fa-solid fa-calendar" data-title="12 May, 9000 " data-toggle="tooltip" data-placement="bottom" aria-hidden="true"></i>
          </div>
        </li>
        <!-- Up to 5 events total -->
      </ul>
    </div>
    <!-- Upcoming events column -->
    <div class="flex flex-col gap-2 ">
    <!-- More of the same -->
    </div>
  </div>
</div>

You could for example target both columns with .widget-calendar .flex-col {...}, or only one of the two by adding :first-child or nth-child(2) respectively; or style all reminder links with .widget-calendar ul a {...} without affecting other links in the widget.

Map preview body

Map previews are too complex to be explored in detail here, but a div.widget-map replaces the typical header and body combination and contains a single div#map(id) with a slew of classes and inline styles. Because of these inline styles, resizing is best done directly on this element by referencing it by ID and using the !important keyword. A few other potentially interesting elements for styling are included in the sample below, namely the Explore button, .leaflet-map-pane which contains the underlying map, and .leaflet-control-container which contains controls such as the zoom buttons and layer selector.

<div class="widget-map">
  <div class="map map-dashboard leaflet-container leaflet-touch leaflet-fade-anim leaflet-grab leaflet-touch-drag leaflet-touch-zoom" id="map1315" style="width: 100%; height: 100%; position: relative;" tabindex="0">
    <a href="https://app.kanka.io/w/.../explore" target="_blank" class="btn2 btn-primary btn-xs btn-map-explore z-[820] absolute bottom-3 right-3">
      <i class="fa-regular fa-map " aria-hidden="true"></i>
      Explore
    </a>
    <div class="leaflet-pane leaflet-map-pane" style="transform: translate3d(7px, 0px, 0px);">
      ...
    </div>
    <div class="leaflet-control-container">
      ...
    </div>
  </div>
</div>

Text header

Text headers are simply a heading (your choice from h1 to h6), optionally wrapped in an anchor if a target entity is provided. These two (or three) layers allow some flexibility regarding borders, backgrounds and padding, in addition to formatting the text, which is very plain by default.

<div class="col-span-12 md:col-span-12 widget widget-header" id="widget-col-205449">
  <a href="https://app.kanka.io/w/.../entities/...">
    <h3 class="widget-header-text text-center my-4 custom-class" id="dashboard-widget-205449">
      Header text
    </h3>
  </a>
</div>

It’s worth noting that a header’s width can be set just like other widgets, so you can get creative with side-by-side blocks. Though for more complex designs, it may be easier to use an entity preview and hide its header, relying solely on the entry’s content for your layout.

Also note that since custom classes added to this type of widget are placed directly on the heading element (for example custom-class above), any outer styling would have to rely on the :has pseudo-class.

Attributes iframe

If you choose the Attributes option in the Display dropdown of an entity preview, the relevant part of the entity’s attribute page will be injected as an iframe element. This can display either a raw list of attributes as dl/dt elements, or a fully customized Marketplace character sheet if one is set for that entity.

<div class="widget-body p-0 ">
  <iframe src="https://app.kanka.io/w/1/entities/1234/attributes-dashboard" class="entity-attributes w-full"></iframe>
</div>

It’s worth noting that iframes are treated by the browser as separate documents. As such, you cannot influence the appearance of an iframe's content with a rule like #campaign-dashboard iframe .wrapper {...} — the dashboard’s CSS doesn’t see anything past the iframe in such a selector. However, container queries can come in handy to help you write width-based rules rather than parent-based ones, and thus make sure your attributes display properly in a narrow widget.

Widget customization examples

Pinned relations as a sidebar

Here is a simple bit of CSS I use on my session log (Entity list widget, in preview mode showing the latest Journal) to turn pinned relations into a sidebar, similar to pins on the entity itself. I use it to show the session’s participating player characters, along with a link to the previous session log. Additional styling can be used, but I am only showing the positioning and spacing properties below to get you started.

/* Turn the content area into a 2-column grid */
#widget-preview-body-52696 {
	margin-top: 15px;
	display: grid;
	grid-template-columns: auto minmax(200px, 20%);
}
/* Justify entity-content for a cleaner look */
#widget-preview-body-52696 .entity-content {
	text-align: justify;
}
/* Make a cleaner separation between relation names and items */
#widget-preview-body-52696 .pinned-relation strong {
	display: block;
}

Add a title to a map widget

For those wanting a consistent look across their widgets, the absence of a header on map previews can be annoying. Although we can’t create a link to the entity, we can at least fake a title using a pseudo-element (this requires targeting each map individually, since we have to specify its name). Here is an example mimicking the default style:

/* Put the name in a ::before pseudo-element, reusing styles from standard headers */
.widget-map::before {
	display: block;
	padding: 1rem;
	font-size: 1.25rem;
	color: var(--header-text, hsl(var(--bc)/var(--tw-text-opacity)));
	line-height: 1.75rem;
}
#dashboard-widget-44291::before {
	content: "Arcadie";
}

And one with a background image (legibility could be better and a border might be nice, but this covers the basics):

.widget-map::before {
	display: block;
	padding: 2rem 1rem;
	font-size: 1.25rem;
	color: var(--header-text, hsl(var(--bc)/var(--tw-text-opacity)));
	text-shadow: rgba(0,0,0,.9) 3px 3px 4px;
	line-height: 1.75rem;
	background-size: cover;
	background-repeat: no-repeat;
	border-top-left-radius: .25rem;
	border-top-right-radius: .25rem;
}
/* Customize the image source and positioning for each widget, but also corresponding text properties to ensure legibility */
#dashboard-widget-44291::before {
	content: "Arcadie";
	background-image: url("https://th.kanka.io/.../image.jpg");
	background-position: 50% 10%;
	color: white;
	text-shadow: rgba(0,0,0,.9) 3px 3px 4px;
}

Custom map preview height

Fairly often, I get people asking how to make maps taller on the dashboard since the default size isn’t really suited to map visualization. Fortunately, that’s a very easy fix as long as you use !important to override the inline height definition:

.map-dashboard {
	height: 400px !important;
}

Custom dashboards

You can create additional dashboards, which can be targeted via a class on the body tag, .dashboard-(id). Note that the default dashboard is not similarly identified, so you have to resort to something like body:not([class*="dashboard-"]) #campaign-dashboard {...} to target it while excluding your custom dashboards, or body:not(.dashboard-2):not(.dashboard-3) #campaign-dashboard {...} to exclude specific ones.

On custom dashboards, the Campaign Header is optional.

Displaying a unique Campaign Header on custom dashboards

Showing the Campaign Header with the same introductory text on every custom dashboard may seem a little boring or unnecessary, but you may still want to include it for its unique aesthetics. Since custom dashboards have a unique ID, we can use it creatively to insert more customized content in this special, full-width section, following a few (admittedly convoluted) steps.

First, edit your campaign Excerpt in the campaign editor (Dashboard tab) and, in Code View, enclose the content you want to display in your main Campaign Header in a unique div (for example <div id="default-excerpt">default header content</div>). This will let you control whether your main presentation should be displayed on each dashboard.

Next, add additional content to your Excerpt in a different div, also giving it a unique ID, such as <div id="excerpt-1">custom dashboard 1 header content</div>. You can repeat this process multiple times for several dashboards.

Once your excerpts are ready, add the "Campaign header" widget to each dashboard (a special type that isn’t offered on the default dashboard) and make note of each dashboard’s ID in the address bar (which should end in ?dashboard=<id>).

Once your excerpts and dashboards are mapped out, all you need to do is hide every excerpt by default, then make each one visible on the corresponding dashboard:

/* Hide all variant excerpts everywhere by default for simplicity.
 * The attribute selector uses |# to match all IDs starting with "excerpt" followed by a dash.
 * Also hide the default excerpt on all custom dashboards. */
div[id|="excerpt"],
body[class*="dashboard-"] #default-excerpt {
	display: none;
}
/* Reset visibility on alternate excerpts by matching dashboard IDs to excerpt IDs */
.dashboard-132 #excerpt-2,
.dashboard-321 #excerpt-3 {
	display: initial;
}

Beyond these variant excerpts, you can of course also customize the appearance of each Campaign Header by targeting .campaign-header, .campaign-content, etc. as appropriate to change the background image source, display a different title or none, etc.

Last updated