Primo gives you the ability to develop your site directly from Primo by embedding a development environment within the CMS's interface. This means you can immediately access your site's code & don't need to spend time setting up a local development environment, while still having the option to use a traditional development environment for more advanced components.
Fields allow you to define form fields that appear in the CMS. When the site is published, the value of those fields is compiled into static HTML. When you create a new field, you need to integrate it with your code using Svelte syntax (see below).
You can define 7 basic input types (text, image, Markdown, number, switch, URL, link), 1 informational type, and 2 composite types (group & repeater).
- Text
- Number
- Image - accessible with
{image.url}
and{image.alt}
- Markdown - accessible with
{@html description.html}
or{description.markdown}
- Switch (true/false)
- URL
- Link - accessible with
{link.url}
and{link.label}
- Info (enables writing instructions to CMS users using Markdown)
- Group (used to group fields together)
- Repeater (used for content lists)
You can set any field as Static, meaning that its value will be the same across all instances of it. This is useful for content that doesn't change across pages, like navigation, social links, and forms. Updating a static content value on one page will update it everywhere.
Primo utilizes Svelte as a templating engine. Svelte is a popular next-generation web development framework used to build interactive web applications. Since Svelte is a superset of HTML and has much more powerful templating capabilities compared to more traditional templating engines like Handlebars, it's the perfect tool for creating simple, powerful components within Primo.
Field values can be accessed from the code using the field's key in order to output values into code, conditionally render content, and more.
Output the value of a text/number field or the value of a JavaScript expression.
<h1>{heading}</h1>
<h2>{heading.toUpperCase()}</h2>
Output the HTML value of Markdown field using the @html
tag inside a text expression and .html
. Its Markdown value can be accessed with .markdown
.
Use the :global() modifier to target tags within the content (e.g. :global(h1) { font-size: 30px })
.
Note that HTML can only properly render within a div
tag as it can contain heading elements. Attempting to render it within a p
or span
will create issues.
<div>{@html content.html}</div>
Conditionally render a block of code. This is particularly useful with the 'Switch' field type.
{#if image.url}
<img src={image.url} />
{:else if heading}
<h2>{heading}</h2>
{:else}
<span>{fallback}</span>
{/if}
{#if show_footer}
<footer>...</footer>
{/if}
In some cases, it may be preferable to simply use an OR or ternary operator within a text expression.
<h1>{heading || title || fallback}</h1>
Iteratively render a Repeater field.
{#each links as link}
<a href={link.url}>{link.label}</a>
{/each}
All CSS written in your Blocks is scoped, meaning that it only applies to HTML within that Block and doesn't affect other Blocks. Styles written in your Page apply to all the Blocks on the page, and styles written in your Site apply to all the Blocks on your site.
For CSS written within your Blocks to apply to the corresponding Block's HTML, the HTML needs to be written out, but in cases where HTML is generated (particularly when rendering a Markdown field) it's necessary to use the global
modifier.
For example, if in your HTML you have:
<div class="content">{@html content.html}</div>
You can target any HTML created within the .content
element with:
.content {
:global(a) {
text-decoration: underline yellow;
}
}
Primo preprocesses your CSS using PostCSS to enable nesting & autoprefixing. For example:
ul {
user-select: none;
li {
color: red;
}
}
Becomes
ul {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul li {
color: red;
}
You can import most JS libraries from your Blocks' JavaScript with either an absolute path (without needing to import them first) or a URL. You can also import your own externally-managed components using this method.
import confetti from 'canvas-confetti'
confetti()
import confetti from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
confetti()
Themes in Primo come with pre-written CSS written at the site level which makes it easier to develop your websites. CSS is included for standardizing styles, setting theme values (for themeing Blocks), and for styling elements shared across Primo Blocks like heading
and button
. Any styles added in Site CSS can be overwritten by Page CSS and Block CSS in a natural cascading order.
These styles standardize how your site looks across different browsers and devices.
CSS Custom Properties (or variables) are used within all Themes to correspond to particular style declarations within their Blocks and Primo Library Blocks, enabling them to adapt to the design of any sites they're added to and have their styles updated from one place. They do this by using these values for particular properties like color
, border-radius
, and box-shadow
which should remain consistent within a site but are variable across different sites (e.g. rounded corners on one site, sharp corners on another).
--color-accent
is a custom design token used to set the site's accent color.
:root {
/* Custom theme tokens */
--color-accent: #5a5a5a;
/* Base values */
--font-family: "Bespoke Serif", serif;
--box-shadow: 0px 4px 30px rgba(0, 0, 0, 0.2);
--border-radius: 8px;
--border-color: #cbcace;
}
These are classes used across most Primo Library components which keep their design consistent. They're listed here in order from most to least frequently used.
-
.heading
applies to all component headings except for those within Hero components. -
.section-container
maintains a fixed max-width and padding for most components, while allowing others to maintain a full page width. -
.link
is for underlined links. -
.button
is for buttons and links styled as buttons.
Primo adds certain classes to Blocks that make it easier to style your site within Primo and prevents styles from affecting the app's UI (e.g. overwriting the toolbar's font family).
-
"
#page
" is added to every page'sbody
tag, and should be used in place ofbody
orhtml
whenever possible. -
"
.section
" is added to the root element of every section. Each section also has a unique ID prefixed with "section-
" (e.g.section-kdl99dl
).
- Setting the preview window to Static enables you to toggle between device types (mobile, tablet, laptop, desktop) or manually set a specific screen width.
- Setting the preview window to Dynamic fixes it to the actual width of the preview pane, allowing you to adjust it by dragging the pane to be larger or smaller.
You can toggle the Editor screen to show the preview on the right side of the code (horizontal) or below it (vertical). Horizontal is typically preferrable, but vertical works better for hiding the preview and showing more of the code, enabling you to hide the preview in vertical mode and show it in horizontal mode so you can quickly switch between showing and hiding the preview.
Primo instantly updates your code changes in the preview by default, but it can be useful to turn this feature off when working with large Blocks to prevent sluggish behavior. In that case, you can refresh the preview by pressing Mod+R (i.e. Command-R
on Mac and Control-R
on Windows).
You can toggle the HTML/CSS/JS code panes with Mod+1
, Mod+2
, and Mod+3
respectively (i.e. Command+1
on Mac and Control+1
on Windows). You may need to click into a code pane first to capture these key events.
Primo will automatically make fields editable on the page by matching a Block's field values to its value within the Block's DOM element, but there may be instances where you'll need to explicitly set a field value as being editable.
All text, link, and image fields will be automatically editable, including those within Repeater and Group fields, so long as they are within their own element without any other text.
<!-- These will be editable on the page -->
<h1>
<span>{heading}</span>
<span>{subheading}</span>
</h1>
<div>
<span>"</span>
<div>{@html quote}</div>
<span>"</span>
</div>
<!-- These will not -->
<h1>{heading} - {subheading}</h1>
<div>"{@html quote}"</div>
You can explicitly set a field value to be on-page editable by setting a data-key
attribute with a value matching the field's key. This should only be necessary in cases where Primo can't automatically detect the field value.
<div data-key="description">{@html description.html}</div>
<!-- Repeater field -->
<ul>
{#each teasers as teaser, i}
<li data-key="teasers[{i}].item">{teaser.item}</li>
{/each}
</ul>