What Formulize expects from a theme
A Formulize theme is an ordinary ImpressCMS theme, and most of it is yours to design however you like. There are a few things Formulize looks for. Get these right and forms, lists and maps behave correctly in your theme; miss them and those things quietly stop working rather than raising an error.
1. Fire formulize_pageShown when the page is revealed
Most themes hide the body while the page assembles and reveal it once everything has loaded. When you
reveal it, dispatch this event on window:
window.dispatchEvent(new CustomEvent('formulize_pageShown'));
Formulize waits for it before putting a reader back where they were on a form, a list or a map. If your theme never fires it, a reader who saves a long form is returned to the top of it every time.
If you don’t hide and reveal the body, fire it once the page has loaded anyway.
2. Say what scrolls, if it isn’t obvious
Formulize saves a reader’s scroll position when they save a form, and puts it back afterwards. To do that it has to know which element scrolls. And when a theme names the element, the mouse wheel or trackpad scrolls it from anywhere on the page, not only with the pointer over it.
Usually you don’t need to do anything. Formulize looks for the nearest ancestor of the form that
is actually scrollable — an element taller than its own box with overflow-y set to auto or
scroll — and falls back to the window. A theme that scrolls the window, and a theme that scrolls a
main content pane, both work without being told.
Declare it when that guess would be wrong — several nested scrollable elements, say. Put the selector
on your <body> tag:
<body data-formulize-scroll-container=".my-main-pane">
The selector can name several elements, for a theme that scrolls a different one at different widths, or on different screens. Formulize uses the first of them that is scrolling at the time, and if none is, looks for one as if nothing were named. Lyris scrolls a list’s entries or a form’s card on wide screens, and the page on phones:
<body data-formulize-scroll-container=".lyris-list__body, .lyris-form-screen, .lyris-main">
Naming it also means a reader can scroll it from anywhere on the page: over the margins beside a
narrow column, a title bar, a bar of buttons. The wheel scrolls the named element unless the pointer
is already over it, or over something else that scrolls on its own, such as an open menu or a wide
table. It is also left alone over a modal and its backdrop (anything with aria-modal="true" or
role="dialog", such as the entry drawer), so the page behind a modal stays where it is, and over a
menu or list of options opened outside the element (role="menu", role="listbox", or jQuery UI’s
.ui-menu, as an autocomplete’s suggestions are). This is include/js/scroll_container.js, which
Formulize adds to every page; a theme that names nothing isn’t affected.
Use none when nothing in the page scrolls, because something outside it does:
<body data-formulize-scroll-container="none">
Then Formulize will not try to save or restore a position at all.
3. Embed themes need a marker file
A theme meant for screens embedded in another website is chosen from a
separate list in the Formulize preferences, and is kept out of the normal theme pickers. Put a file
named formulize-embed-theme.marker in the theme folder to mark it as one. A theme without the
marker is not used for embedded screens, even if it is chosen in the preferences.
The simplest way to make your own is to copy the themes/formulize_embed folder. Keep these in your
copy:
- the marker file
- the short script just after the
<body>tag intheme.html, which turns off the page’s own scrolling when it is inside a frame - the script at the bottom of
theme.html, which is how an embedded screen reports its height to the page hosting it session-timeout-warning.html, including itssession-timeout-warningid, which the script at the bottom uses to bring the warning into view
4. Style screens so they still work without your header and menus
An embedded screen borrows your theme’s styling without its page layout. It loads your
css/reset.css, if you have one, then your css/style.css, and then any colours, font and logo set in
the Appearance editor. It does not load your theme.html or your script. The <body> has the class
formulize-inline.
Two optional files let you adjust how your theme looks when embedded:
-
embed-content.html: the elements your theme puts around the page content, with<{$icms_contents}>inside them and your header and menus left out. Add it if your stylesheet styles screens through those elements. Without it, the screen is drawn inside a plain<main>.<div class="my-layout"> <main class="my-main-pane"> <{$icms_contents}> </main> </div> -
css/embed.css: loaded after your other stylesheets, only on embedded screens. Use it for anything that assumes your page fills the window. An embedded screen is exactly as tall as its content, so a content area that fills the window and scrolls on its own should just be as tall as its content:.my-main-pane { height: auto; overflow: visible; }
The Anari theme has both files, if you want an example.
5. Give every page the same space at its edges
Every page should have the same space between its content and the edges of the window, your header and your menu, whatever is on the page. Formulize has several types of screen (list of entries, form, template, calendar and map), and more will be added. None of them should look different from the others in this, and neither should the pages that are not screens, such as an application’s menu page.
Formulize doesn’t supply that space, and no type of screen brings its own. Your theme does, in one
place: put a wrapper around all the content in your theme.html, and give it padding.
<main class="my-main">
<div class="my-page">
<{$icms_contents}>
</div>
</main>
.my-page {
padding: var(--fz-page-padding);
}
--fz-page-padding is the Appearance editor’s Page padding, so the space follows that setting. Then
there is nothing more to do: a new type of screen gets the space without any change to your theme,
and a screen inside another (a template screen’s code can call displayForm or displayEntries) is
not inset twice, because neither has space of its own.
Make the wrapper its own element, inside the part of your page that scrolls, and leave the scrolling part without padding. Then the wrapper is also the place to keep pages to a maximum width (section 7), and anything your theme pins to the top or bottom of the scrolling part is not pushed in by it.
Don’t put the space on the parts of a screen instead, in your screen templates. A screen drawn with other templates would have none, and a screen inside another would have it twice.
On a phone you may want less space, or want your list and form screens to use the whole width of the
screen. Make the padding smaller there, and take it off only for the pages you mean. Lyris does both:
see “The page frame” and “Mobile” in themes/Lyris/css/style.css. Content should never touch the
edge of the screen, so anything that reaches the edge needs padding of its own inside it.
If your embed stylesheet or your embed-content.html (above) is used, leave the space out there:
the page your screen is embedded in supplies it.
6. Work with the Appearance editor (optional)
The Appearance editor edits a theme’s appearance on a preview of
sample screens the theme provides (below): a theme without them can’t be edited there. In simple
mode it changes the logo, colours, fonts and page width, which every theme that calls
formulize_renderAppearanceHead() follows.
Its advanced mode, and the Compact and Comfortable looks, change Formulize UI’s component tokens: sizes (--fz-field-height, --fz-row-height, --fz-title-text),
fonts (--fz-label-font), colours (--fz-button-bg, --fz-header-bg) and corners
(--fz-field-radius), and the rest. They only do anything in a theme whose own CSS styles things with
those tokens, so a theme says when it does, and only then does the editor offer them. Each
colour and font token defaults to the palette colour or font it stands for, so a theme that uses them
looks the same until one is changed.
Opt in by declaring --formulize-size-tokens on :root in your css/tokens.css:
:root {
--formulize-size-tokens: 1;
}
If your theme’s own font comes from Google Fonts, say which weights of it to download, with your other tokens:
:root {
--fz-font-sans: Poppins, Helvetica, sans-serif;
--formulize-font-weights: 400 500 600;
}
--formulize-font-weights is a list of numbers with spaces between them. Each number is a
font-weight that your stylesheet uses: 400 is regular, 700 is bold, and the others are the steps
around them. A webfont is a separate file for each weight, and only the weights you list are
downloaded.
- List every weight your CSS uses. Text set in a weight you left out is shown in the nearest weight
you did list: with
400 500 600, bold (700) text is shown at 600. - List only weights the font has. Its page on Google Fonts shows them. If you list one it doesn’t have, the font isn’t loaded at all.
- Leave out the weights you don’t use. Each one is another file for the browser to fetch.
The font that is loaded is the first one named in --fz-font-sans, so that name has to be its name
on Google Fonts. It is loaded on every page that has your theme’s appearance: the pages your
theme.html draws, the popups of the list screens, and screens embedded in another site. When
another font is chosen in the Appearance editor, yours is no longer loaded.
Don’t load the font in theme.html as well. A theme that leaves --formulize-font-weights out gets
Geist, the font Lyris uses.
Provide sample screens for the editor’s preview, in an appearance_preview folder
in your theme. Each is an HTML file named after a screen: form.html, list.html, drawer.html and
cards.html. Provide the ones that suit your theme; the editor shows the ones it finds, and without
any, the theme can’t be edited there. A sample is the markup your theme puts in <body> for
that kind of page, written out with sample content, and it is shown with your css/reset.css, your
css/style.css and your generated appearance stylesheet, in a <body> with the id formulize and
the class formulize-screen. No scripts run in it.
Mark each part of the sample that can be selected with data-fz-part, naming the part:
<input type="button" class="formulize-form-submit-button" value="Save" data-fz-part="button">
The parts, and the tokens of each one, are the components in
modules/formulize/include/appearance_tokens.json: logo (the link around your logo image),
page, tabs, title, form, label, field, value (a read-only value), options (radio
buttons and checkboxes), help, button, toolbar, menu, header (column headings), row,
card and drawer. The editor shows a new logo by changing the src of the image inside the
logo part.
Pieces shared between samples go in files starting with an underscore, and are included by name in
double braces: is the contents of `_list.html`. and `` are your
logo and the site’s name. Clicking an element with data-fz-toggle="some-id" in the preview toggles
the class open on the element with that id, for showing a menu.
Lyris’s samples, in themes/Lyris/appearance_preview/, are a complete example. A theme edited in simple
mode only, such as Anari (themes/Anari/appearance_preview/), only needs to mark its logo, as the
logo part.
7. Offer the Page width setting (optional)
The Page width setting, in the Appearance editor’s site-wide settings, keeps
pages to a maximum width on a wide screen, or lets them use the full width of the window. It sets
--formulize-content-max-width: a width in pixels, or 100% for full width. Laying the page out to
that width is up to the theme, so a theme says when it does, and only then is the setting offered.
Opt in by declaring --formulize-content-max-width on :root in your css/tokens.css. What you
declare is your theme’s own width, which is what the setting starts at and what a reset goes back to:
a width in pixels, or 100% to start at full width. Lyris declares 1200 pixels:
:root {
--formulize-content-max-width: 1200px;
}
Then use it as the maximum width of your content, on the wrapper that has the space at the page’s
edges (section 5), so that every type of screen is kept to it and none needs anything of its own.
Lyris makes its pages a column of that width, centred in the window. In it, each part of a list or a
form is a card: a list’s title bar, its entries, a form, and a floating bar at the bottom for a
list’s pagination or a form’s buttons. The column is centred in the window rather than beside the
sidebar, so opening the sidebar doesn’t move it unless it has to; the header’s links stay at the
window’s edge; and phones are left full width. See the “Content width” section at the end of
themes/Lyris/css/style.css.
If your theme has a menu beside the content and you want the column centred in the window, not in
the space beside the menu, the wrapper is what makes that possible. The room to leave either side
depends on how wide the content area is, and in CSS a percentage in an element’s padding is measured
against its parent, not itself. So the content area can’t work it out for itself, but the wrapper
inside it can: on the wrapper, 100% is the content area’s width.
A page that fills the window and scrolls inside itself, as Lyris’s lists and forms do, needs the
wrapper to be exactly as tall as the scrolling part. Give the wrapper min-height: 100% for every
page, and height: 100% only on the pages that fill the window. Every other page is then as tall as
its content, and the page scrolls.
On paper, take the wrapper’s padding and height off again, with the rest of your theme’s layout (see Printing, under For theme authors, in Formulize UI).
A screen can be set to use the full width whatever the site’s setting is, on the screen’s Appearance
tab. Formulize sets --formulize-content-max-width to 100% on that screen’s page, so a theme that
uses the property as above has nothing more to do.
Checking your theme
Open a long form in your theme, scroll down, and save it. You should be returned to where you were
rather than to the top of the page. If you are not, the theme is either not firing
formulize_pageShown or scrolling an element Formulize could not find.
Open a screen of each type you have, and an application’s menu page. On every one, the content should have the same space around it, not sit against the edge of the window or your header. If you have a template screen with a form or a list inside it, check that the form or list lines up with the rest of that page, not further in.