For theme authors

How a theme works with Formulize UI: giving the tokens its values, loading the stylesheet, keeping its own rules from getting in the way of the classes, and framing the page that everything is on.

Giving the tokens values

Formulize UI sets a neutral default for every token. A theme gives them its own values in a :root rule, and the colours and font set on the Appearance page then override the theme's.

  • Set the tokens, rather than restyling the classes: every class is built on them, so the theme's look reaches all of them at once, and the Appearance page keeps working.
  • The sizes are in rem, so the browser's font size scales them. Keep a theme's own sizes in rem too.

In the theme's stylesheet

:root {
  --fz-color-accent: #0f5e9c;
  --fz-color-accent-hover: #0b4a7a;
  --fz-color-accent-soft: #e7f0f8;
  --fz-font-sans: "Source Sans 3", system-ui, sans-serif;
  --fz-radius-lg: 0.25rem;
}

Loading the stylesheet

Formulize adds formulize-ui.css to its own pages. A theme adds it to every other page by calling formulize_uiStylesheetLink() in its theme.html, before the theme's own stylesheet.

  • The function returns nothing on a page that already has the stylesheet, so it is never loaded twice.
  • The order matters: formulize-ui.css first, then Formulize's own stylesheet, then the theme's. Where a theme rule and a class have the same weight, the theme wins.

In theme.html, in the head

<{php}>
require_once XOOPS_ROOT_PATH . '/modules/formulize/include/functions.php';
echo formulize_uiStylesheetLink();
<{/php}>
<link rel="stylesheet" type="text/css" media="all" href="<{$icms_imageurl}>css/style.css" />

Keeping theme rules out of the way

Every public class is a single class selector, with no !important, so a utility or a modifier can always adjust it. A theme keeps that working by not outweighing the classes.

  • Give the theme's own classes the theme's prefix, such as lyris-, never fz-.
  • When styling Formulize's own markup, wrap the selector in :where(), which gives it no weight, so a class added to that markup still wins.
  • A rule that styles every element of a kind, such as every button or every checkbox, would also restyle the Formulize UI components. Add :where(:not([class*="fz-"])) to it: it skips any element with a Formulize UI class, and adds no weight, so the rule otherwise works as before.

In the theme's stylesheet

/* Formulize's own markup: no weight, so classes added to it still win */
:where(#formulize-list-of-entries) td {
  padding: 0.5rem 0.75rem;
}

/* Every button, except the Formulize UI ones */
button:where(:not([class*="fz-"])),
input[type="submit"]:where(:not([class*="fz-"])) {
  min-width: 8rem;
  border-radius: 0;
}

Framing the page

The space at the edges of a page, and how wide a page can get, are the theme's to supply, in one place, for every page. No type of screen brings its own, and what people write for a screen starts at the edge of what the theme gives it.

  • Put one wrapper around all of a page's content in theme.html, and give it padding: var(--fz-page-padding), the Appearance page's Page padding. Every type of screen then has the same space, including the types added later, and a screen inside another is not inset twice.
  • To offer the Page width setting, keep the same wrapper to --formulize-content-max-width. A screen set to the full width has that property at 100% on its own page.
  • Make the wrapper its own element, inside the part of the page that scrolls, and leave the scrolling part without padding.
  • Don't put the page's space on the parts of a screen, in screen templates: a screen drawn with other templates would have none.
  • On a phone the space can be smaller, and a screen made of bars and cards can run to the edges of the screen, as long as nothing in it touches them.
  • The whole of this, with what Lyris does, is in What Formulize expects from a theme, in the documentation.

In theme.html

<main class="mytheme-content">
  <div class="mytheme-page">
    <{$icms_contents}>
  </div>
</main>

In the theme's stylesheet

.mytheme-page {
  box-sizing: border-box;
  max-width: var(--formulize-content-max-width);
  margin-inline: auto;
  padding: var(--fz-page-padding);
}

Printing

A page can be made for printing with the Print classes, which decide what of the page's own content is on paper. The theme does the rest: on paper it leaves out its own header and menus, and lets the content run on over as many sheets as it needs.

  • In an @media print rule, hide the parts of the theme that are for getting around the site: the header, the menus, the footer.
  • Anything the theme makes scroll on screen stops at its own bottom edge on paper. Give it height: auto and overflow: visible there, so it is as tall as what is in it.
  • Take the space at the edges of the page off as well: the sheet's own margin does that on paper.
  • These rules have to win over the theme's own layout whatever it is, so !important is at home in them.

In the theme's stylesheet

@media print {
  .mytheme-header,
  .mytheme-menu,
  .mytheme-footer {
    display: none !important;
  }
  .mytheme-content {
    height: auto !important;
    overflow: visible !important;
    padding: 0 !important;
  }
}