Skip to main content

Every house component, rendered from the templates and the stylesheet this instance ships. Copy the source under a component. When you add a component, add its entry here (base/templates/styleguide/entries/); manage.py check_catalogue fails without one.

Tokens

Every token at Bootstrap 5.2's stock value, without any instance stylesheet. A component that still looks like this platform here paints a value no token controls.

Token Sample Value here Role
--bs-primary primary action, links, brand
--bs-primary-rgb the same colour, for Bootstrap's utilities
--bs-secondary secondary (outline) actions
--bs-secondary-rgb the same colour, for Bootstrap's utilities
--bs-body-color text
--bs-body-color-rgb the same colour, for Bootstrap's utilities
--oep-text-muted secondary text
--oep-heading-color platform headings
--bs-border-color borders, dividers
--oep-border-strong field borders
--oep-surface cards, overlays
--oep-surface-subtle light bands, the header band, table heads
--oep-surface-strong blue bands (hero, academy, footer)
--oep-surface-hover hover tint (the navbar dropdown's)
--oep-on-strong text on blue bands
--oep-on-strong-muted muted text on blue bands
--bs-success status: success, never an action
--bs-success-rgb the same colour, for Bootstrap's utilities
--bs-info status: information, never an action
--bs-info-rgb the same colour, for Bootstrap's utilities
--bs-warning status: warning, never an action
--bs-warning-rgb the same colour, for Bootstrap's utilities
--bs-danger status: error; destructive actions
--bs-danger-rgb the same colour, for Bootstrap's utilities
--bs-border-radius in the page: buttons, fields, cards
--bs-border-radius-sm small buttons, badges
--bs-border-radius-lg what floats: dropdowns, dialogs
--bs-border-radius-pill tags
--oep-shadow-sm cards
--oep-shadow-lg overlays: dropdowns, dialogs, toasts
--oep-shadow-rgb the tint of both shadows
--bs-font-sans-serif the font (a system stack, no web font)
--bs-body-font-size text
--oep-font-size-dense tables, badges, sidebars, buttons, fields
--oep-heading-weight headings
--oep-measure reading width for prose
--oep-content-width content column and header band; 82, 108, 132 or 160rem by screen width
--oep-transition hover motion

alert

A status message on the page: the result of an action, a warning about the page's subject, an error. Light background, a 4px bar and an icon in the status colour. Write it with the include; a script's `alert alert-<status>` gets the same look.

Your profile was saved.
This table is a draft; only you can see it.
The metadata does not name a license yet.
<div class="d-grid gap-2">
  {% include "components/alert.html" with status="success" text="Your profile was saved." %}
  {% include "components/alert.html" with status="info" text="This table is a draft; only you can see it." %}
  {% include "components/alert.html" with status="warning" text="The metadata does not name a license yet." %}
  {% include "components/alert.html" with status="danger" text="The upload failed in line 12, column 3." dismissible=True %}
</div>

badge

A status beside its subject (Published, Draft, Early Access, what the viewer may do), or a neutral count; never a tag and never a pill.

Published Draft Early Access Embargoed 12

<p class="d-flex flex-wrap gap-2 align-items-center mb-0">
  <span class="badge badge--success">Published</span>
  <span class="badge badge--warning">Draft</span>
  <span class="badge badge--info">Early Access</span>
  <span class="badge badge--danger">Embargoed</span>
  <span class="badge badge--neutral">12</span>
</p>

button

Every action, in one of four roles on the element itself: primary for the one main action of a view or dialog, secondary for the rest, danger outlined on the button that opens a confirmation and solid only inside it, link for an action that reads as a link; on a blue band the light button. Green, cyan, yellow and black are status colours, never a button.

Disabled link button
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
  <button type="button" class="btn btn-primary">Save changes</button>
  <button type="button" class="btn btn-secondary">Cancel</button>
  <button type="button" class="btn btn-outline-danger">Delete…</button>
  <button type="button" class="btn btn-danger">Delete table</button>
  <button type="button" class="btn btn-link">Show details</button>
</div>
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
  <button type="button" class="btn btn-primary btn-sm">Small primary</button>
  <button type="button"
          class="btn btn-secondary btn-sm">Small secondary</button>
  <button type="button"
          class="btn btn-outline-danger btn-sm">Small danger</button>
  <button type="button" class="btn btn-link btn-sm">Small link</button>
</div>
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
  <button type="button"
          class="btn btn-primary"
          disabled>Disabled primary</button>
  <button type="button"
          class="btn btn-secondary"
          disabled>Disabled secondary</button>
  <a class="btn btn-secondary disabled"
     aria-disabled="true">Disabled link button</a>
  <button type="button" class="btn btn-primary">
    <span class="btn__icon fas fa-plus" aria-hidden="true"></span>With an icon
  </button>
</div>
<div class="d-flex flex-wrap gap-2 align-items-center p-3 rounded"
     style="background-color: var(--oep-surface-strong)">
  <a class="btn btn-on-strong btn-sm" href="#">Sign up</a>
  <a class="btn btn-on-strong" href="#">On a blue band</a>
</div>

card

One item of a list or grid (a dataset, a topic, an organization) or one block of a page. Clickable when the whole card leads to one page: its title link is a stretched link, and nothing else in it is a link.

A plain card

Holds what belongs together: a title, a sentence or two, and the links of its own.

A link of its own

A clickable card

The whole card opens the page its title names.

<div class="row g-3">
  <div class="col-12 col-md-6">
    <div class="card h-100">
      <div class="card-body">
        <h3 class="card-title">A plain card</h3>
        <p class="card-text">
          Holds what belongs together: a title, a sentence or two, and the links of its own.
        </p>
        <a href="#card">A link of its own</a>
      </div>
    </div>
  </div>
  <div class="col-12 col-md-6">
    <div class="card card--clickable h-100">
      <div class="card-body">
        <h3 class="card-title">
          <a class="stretched-link"
             href="{% url 'dataedit:topic-list' %}">A clickable card</a>
        </h3>
        <p class="card-text">The whole card opens the page its title names.</p>
      </div>
    </div>
  </div>
</div>

confirm_dialog

Before an action the user may regret: a title naming the action, what will happen, Cancel and the verb (solid red when it cannot be undone; typed for a high bar). Never the browser's confirm(); a template asks with hx-confirm or data-confirm, a script with window.oep.confirm.

<div class="d-grid gap-4">
  {% include "components/confirm_dialog.html" with as_example=True title="Publish table?" text="Everyone can then find and read the table." verb="Publish" %}
  {% include "components/confirm_dialog.html" with as_example=True title="Remove member?" text="Ada Lovelace leaves Grid research and loses every permission the organization gave her." verb="Remove member" danger=True %}
  {% include "components/confirm_dialog.html" with as_example=True title="Delete table?" text="The table, its data and its metadata are deleted. This cannot be undone." verb="Delete table" danger=True typed="wind_farms" %}
  <div class="d-flex flex-wrap gap-2">
    <button type="button"
            class="btn btn-secondary"
            data-confirm="Everyone can then find and read the table."
            data-confirm-title="Publish table?"
            data-confirm-verb="Publish">Try plain</button>
    <button type="button"
            class="btn btn-outline-danger"
            data-confirm="Ada Lovelace leaves Grid research and loses every permission the organization gave her."
            data-confirm-title="Remove member?"
            data-confirm-verb="Remove member"
            data-confirm-danger>Try danger</button>
    <button type="button"
            class="btn btn-outline-danger"
            data-confirm="The table, its data and its metadata are deleted. This cannot be undone."
            data-confirm-title="Delete table?"
            data-confirm-verb="Delete table"
            data-confirm-danger
            data-confirm-typed="wind_farms">Try typed</button>
  </div>
</div>

A short list of choices or actions behind one button or tab; every dropdown looks like the navbar's, with no class of its own.

<div class="d-flex flex-wrap gap-4 align-items-start">
  <div class="dropdown">
    <button class="btn btn-link dropdown-toggle"
            type="button"
            data-bs-toggle="dropdown"
            aria-expanded="false">Metadata options</button>
    <ul class="dropdown-menu">
      <li>
        <h6 class="dropdown-header">Download</h6>
      </li>
      <li>
        <a class="dropdown-item" href="#dropdown">JSON</a>
      </li>
      <li>
        <a class="dropdown-item" href="#dropdown">Open the metadata API page</a>
      </li>
      <li>
        <hr class="dropdown-divider" />
      </li>
      <li>
        <a class="dropdown-item" href="#dropdown">Edit</a>
      </li>
    </ul>
  </div>
  <ul class="dropdown-menu d-block position-static"
      aria-label="The same menu, shown open">
    <li>
      <h6 class="dropdown-header">Download</h6>
    </li>
    <li>
      <a class="dropdown-item" href="#dropdown">JSON</a>
    </li>
    <li>
      <a class="dropdown-item" href="#dropdown">Open the metadata API page</a>
    </li>
    <li>
      <hr class="dropdown-divider" />
    </li>
    <li>
      <a class="dropdown-item" href="#dropdown">Edit</a>
    </li>
  </ul>
</div>

empty_state

Where a list or a search has nothing to show: a muted icon, one sentence saying so, at most one action (what makes the list fill). Compact inside a card or a drawer. An error page is one at page size: an image (image, image_alt) instead of the icon, and with heading the title is the page's one h1.

You have no datasets yet

A dataset collects tables under one title.

No tables are assigned yet.

OEPlatypus

This page could not be found!

It might be an old link or maybe the data moved.

{% load static %}
{% include "components/empty_state.html" with icon="fas fa-layer-group" title="You have no datasets yet" text="A dataset collects tables under one title." action_template="styleguide/entries/_empty_state_action.html" %}
<div class="card">
  <div class="card-body">
    {% include "components/empty_state.html" with icon="fas fa-table" text="No tables are assigned yet." compact=True %}
  </div>
</div>
{% static 'img/OpenEnergyFamily_OEPlatypus.png' as platypus %}
{% include "components/empty_state.html" with image=platypus image_alt="OEPlatypus" heading=True as_example=True title="This page could not be found!" text="It might be an old link or maybe the data moved." %}

field

Every form field: Bootstrap's form-control, form-select and form-check, label above, muted help and the error below, never a table. Render a Django form with django-bootstrap5 ({% bootstrap_field %}); allauth's forms get it from their element overrides. On these classes set longhands only: a padding shorthand on a select deletes the room for its arrow.

Lower case letters, digits and underscores.
Enter a valid email address.
<div class="row g-4">
  <div class="col-md-6">
    <div class="mb-3">
      <label class="form-label" for="field-name">Table name</label>
      <input class="form-control"
             type="text"
             id="field-name"
             placeholder="e.g. wind_farms_2024"
             aria-describedby="field-name-help" />
      <div class="form-text"
           id="field-name-help">Lower case letters, digits and underscores.</div>
    </div>
    <div class="mb-3">
      <label class="form-label" for="field-email">E-Mail</label>
      <input class="form-control is-invalid"
             type="email"
             id="field-email"
             value="not-an-address"
             aria-describedby="field-email-error" />
      <div class="invalid-feedback"
           id="field-email-error">Enter a valid email address.</div>
    </div>
    <div class="mb-3">
      <label class="form-label" for="field-description">Description</label>
      <textarea class="form-control" id="field-description" rows="3"></textarea>
    </div>
    <div class="mb-3">
      <label class="form-label" for="field-disabled">Current address</label>
      <input class="form-control"
             type="email"
             id="field-disabled"
             value="owner@example.org"
             disabled />
    </div>
  </div>
  <div class="col-md-6">
    <div class="mb-3" style="max-width: 12rem">
      <label class="form-label" for="field-narrow">The license under which the data of this table is published</label>
      <select class="form-select" id="field-narrow">
        <option>Creative Commons Attribution 4.0 International</option>
        <option>Open Data Commons Open Database License</option>
      </select>
    </div>
    <div class="mb-3">
      <label class="form-label" for="field-small">Rows per page</label>
      <select class="form-select form-select-sm" id="field-small">
        <option>25</option>
        <option>100</option>
      </select>
    </div>
    <div class="form-check">
      <input class="form-check-input"
             type="checkbox"
             id="field-check"
             checked />
      <label class="form-check-label"
             for="field-check">Notify me about reviews</label>
    </div>
    <div class="form-check mb-3">
      <input class="form-check-input"
             type="radio"
             name="field-radio"
             id="field-radio" />
      <label class="form-check-label" for="field-radio">Only this table</label>
    </div>
  </div>
</div>

field_help

The "?" beside a field's label when the label alone does not say what belongs in the field; its help opens on hover and on keyboard focus. Not for text every reader needs, which goes below the field as its help text.

Methodical focus

<p>
  Methodical focus
  {% include "components/field_help.html" with label="Methodical focus" text="The one to three areas the model concentrates on." %}
</p>

A "go to ..." link that stands on its own line or at the foot of a card, never inside running text; its arrow slides on hover.

<a class="link-with-arrow" href="{% url 'dataedit:topic-list' %}">
  Explore the database
  {% include "icons/arrow_right.html" %}
</a>

page_header

The band under the navbar on every page except the account and error pages: the area with its icon, then the trail back (at most two links) ending in the page's subject, its one h1. Optional: one muted line, the page's actions (at most one primary), a tab row on its lower edge (the dashboard's, with the gear aside link to Settings, #2653). On a page the title is its h1; here, `as_example` keeps the catalogue's one h1.

<div class="d-grid gap-3">
  {% include "components/page_header.html" with area="About" icon="icons/area_about.html" crumb_label="Overview" crumb_url="#page_header" title="Open Energy Family Steering Committee" as_example=True %}
  {% include "components/page_header.html" with area="Account" icon="icons/area_account.html" crumb_label="Dashboard" crumb_url="#page_header" crumb2_label="Settings" crumb2_url="#page_header" title="Update account profile" description="One muted line, when the page has one." actions="styleguide/entries/_page_header_actions.html" as_example=True %}
  {% include "components/page_header.html" with area="Dashboard" icon="icons/area_dashboard.html" title="A user's name" tabs="styleguide/entries/_page_header_tabs.html" as_example=True %}
</div>

pagination

The pager under a paged list: separate buttons, the current page solid, an optional muted range at the end. DataTables draws the same pager.

<nav aria-label="Pages">
  <ul class="pagination mb-0">
    <li class="page-item disabled">
      <span class="page-link" aria-hidden="true">‹</span>
    </li>
    <li class="page-item active" aria-current="page">
      <span class="page-link">1</span>
    </li>
    <li class="page-item">
      <a class="page-link" href="#pagination" aria-label="Page 2">2</a>
    </li>
    <li class="page-item">
      <a class="page-link" href="#pagination" aria-label="Page 3">3</a>
    </li>
    <li class="page-item disabled">
      <span class="page-link" aria-hidden="true">…</span>
    </li>
    <li class="page-item">
      <a class="page-link" href="#pagination" aria-label="Page 83">83</a>
    </li>
    <li class="page-item">
      <a class="page-link" href="#pagination" aria-label="Next page">›</a>
    </li>
    <li class="pagination__range">1–25 of 2,068</li>
  </ul>
</nav>

section_card

One section of a record page (a factsheet): its heading is a button that shows and hides the section, and every section starts open. Stack them one under the other; not for a single block that never closes, that is a card.

The section's content: here a sentence, on a record page a table of its fields.

{% include "components/section_card.html" with id="section-card-example" title="General information" body="styleguide/entries/_section_card_body.html" %}

section_heading

A section of a page's content starts with a plain h2 (a subsection with h3), no class; the spacing above and below comes with it, and a heading that opens its container gets no space above.

Sources and licences

Body text is 1rem. A section heading gets 2.5rem above and 0.75rem below, a subsection 1.75rem and 0.5rem.

Licence of the data

Headings take the heading colour and weight from tokens.

A minor heading

An h4 gets 1.25rem above; it is smaller than an h3.

<h2>Sources and licences</h2>
<p>
  Body text is 1rem. A section heading gets 2.5rem above and 0.75rem below, a
  subsection 1.75rem and 0.5rem.
</p>
<h3>Licence of the data</h3>
<p>Headings take the heading colour and weight from tokens.</p>
<h4>A minor heading</h4>
<p class="mb-0">An h4 gets 1.25rem above; it is smaller than an h3.</p>

table

Any table of records (.table, plus .table-hover when a row is something to look at) or of labels and values (.table-pairs). Round corners and a border only through the .table-container wrapper, which also scrolls a wide table. DataTables draws the same table.

Table Topic Rows
wind_farm_capacity Energy supply 4
heat_demand_2030 Demand 8,760
grid_nodes Grid 1,204
Finished 2026-10-12
Reviewer A reviewer
Badge Gold
<div class="d-grid gap-3">
  <div class="table-container">
    <table class="table table-hover">
      <thead>
        <tr>
          <th scope="col">Table</th>
          <th scope="col">Topic</th>
          <th scope="col">Rows</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><a href="#table">wind_farm_capacity</a></td>
          <td>Energy supply</td>
          <td>4</td>
        </tr>
        <tr>
          <td><a href="#table">heat_demand_2030</a></td>
          <td>Demand</td>
          <td>8,760</td>
        </tr>
        <tr>
          <td><a href="#table">grid_nodes</a></td>
          <td>Grid</td>
          <td>1,204</td>
        </tr>
      </tbody>
    </table>
  </div>
  <div class="table-container">
    <table class="table table-pairs">
      <tbody>
        <tr>
          <th scope="row">Finished</th>
          <td>2026-10-12</td>
        </tr>
        <tr>
          <th scope="row">Reviewer</th>
          <td>A reviewer</td>
        </tr>
        <tr>
          <th scope="row">Badge</th>
          <td>Gold</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

tabs

Switches between views of one thing (panes of a page, or the pages of one area); the current tab is marked, at most one icon link to somewhere else sits at the row's end, and an action never sits in the row.

<nav class="nav tabs mb-4" aria-label="Tabs without an aside link">
  <a class="nav-link active"
     aria-current="page"
     href="#tabs"><i class="fas fa-table" aria-hidden="true"></i> Tables</a>
  <a class="nav-link"
     href="#tabs"><i class="fas fa-database"
   aria-hidden="true"></i> Datasets</a>
  <a class="nav-link"
     href="#tabs">Reviews <span class="badge badge--info">Early Access</span></a>
  <a class="nav-link" href="#tabs">Organizations</a>
</nav>
<nav class="nav tabs" aria-label="Tabs with an aside link">
  <a class="nav-link"
     href="#tabs"><i class="fas fa-table"
   aria-hidden="true"></i> Tables</a>
  <a class="nav-link active"
     aria-current="page"
     href="#tabs"><i class="fas fa-database" aria-hidden="true"></i> Datasets</a>
  <a class="nav-link" href="#tabs">Reviews</a>
  <a class="nav-link" href="#tabs">Organizations</a>
  <a class="tabs__aside"
     href="#tabs"
     aria-label="Settings"
     title="Settings"><i class="fas fa-cog" aria-hidden="true"></i></a>
</nav>

tag

One Tag wherever it appears, in its own colour: static to show it, link to filter by it (or to open its editor where tags are administered), removable in a picker's current selection, toggle in a pick list. Never for a status; that is a badge.

Wind Electricity Heat A topic, no colour
<div class="tag-group">
  {% include "components/tag.html" with name="Wind" color="#2e7d32" %}
  {% include "components/tag.html" with name="Electricity" color="#f9a825" href="?tags=electricity" title="Filter by Electricity" %}
  {% include "components/tag.html" with name="Heat" color="#6a1b9a" remove="heat" %}
  {% include "components/tag.html" with name="Hydrogen" color="#00838f" toggle_name="catalogue_tags" toggle_value="hydrogen" checked=True %}
  {% include "components/tag.html" with name="Mobility" color="#ad1457" toggle_name="catalogue_tags" toggle_value="mobility" %}
  {% include "components/tag.html" with name="A topic, no colour" %}
</div>

toast

Short feedback that must not move the page: an action done in the background, or an error a script ran into. Never the browser's alert(); a script calls window.oep.toast(text, {status, title}). A success goes away by itself, an error stays until closed. Feedback that belongs to the page is an alert.

Your profile was saved.
The review was saved as a draft.
Not published The metadata does not name a license yet.
Delete failed The table could not be deleted. Try again later.
<div class="d-grid gap-2 justify-content-start">
  {% include "components/toast.html" with status="success" text="Your profile was saved." %}
  {% include "components/toast.html" with status="info" text="The review was saved as a draft." %}
  {% include "components/toast.html" with status="warning" title="Not published" text="The metadata does not name a license yet." %}
  {% include "components/toast.html" with status="danger" title="Delete failed" text="The table could not be deleted. Try again later." %}
</div>

toc

The "On this page" list of a long text page (about, the legal pages): its entries are the page's own headings, in order, each linking to its heading's id. Put it first in a .toc-layout, the text in .toc-layout__body: a sticky column on the left from 992px up, above the text below that. One per page; not for short pages or lists. Something that belongs above the list (the terms of use's language switch) goes with it into a .toc-layout__side, which then takes the list's place.

A first section

Running text, as wide as the reading measure. The list beside it repeats this page's headings word for word.

A second section with a longer heading that wraps

A long entry wraps inside its column.

<div class="toc-layout">
  {% include "components/toc.html" with entries="styleguide/entries/_toc_entries.html" %}
  <div class="toc-layout__body">
    <h3 id="toc-example-first">A first section</h3>
    <p>
      Running text, as wide as the reading measure. The list beside it repeats
      this page's headings word for word.
    </p>
    <h3 id="toc-example-second">A second section with a longer heading that wraps</h3>
    <p>A long entry wraps inside its column.</p>
  </div>
</div>

user_menu

The navbar's account corner, once per page, last in the navbar's collapse; signed in a menu under the user's name, signed out Sign in and the bar's one Sign up. Never anywhere else.

<div class="d-flex flex-column gap-3">
  <nav class="navbar navbar-expand-lg navbar-dark bg-primary index-navbar"
       aria-label="Signed in">
    <div class="container-fluid">
      {% include "components/user_menu.html" with user=example_user id="catalogueUserMenu" %}
    </div>
  </nav>
  <nav class="navbar navbar-expand-lg navbar-dark bg-primary index-navbar"
       aria-label="Signed out">
    <div class="container-fluid">
      {% include "components/user_menu.html" with user=None %}
    </div>
  </nav>
</div>