Design system

Page settings dialog

Everything about a landing page apart from where it is published: its name, access, search and sharing previews, tracking and forms.

import { PageSettingsDialog } from "@/pages/library/PageSettings";
import "@/pages/library/library.css";

PageSettingsDialog holds the settings that belong to the page itself, whether or not it is live. Where the page is published is the publish dialog's job. On a live page, saved settings are held back until the page is published again, like any other change, and the page builder's toast says so.

The dialog is large (800px) and its body scrolls, in six groups: General, Access control, SEO settings, Open Graph settings, Tracking and Forms. The search and share previews update as people type.

A new draft

Every setting starts from its default, filled in by metaOf(page). The search title falls back to the page name, there is no description yet, and the Open Graph image is a snapshot of the page's first two sections.

{settingsOpen && (
  <PageSettingsDialog
    page={page}
    address={live ? pageUrl(page) : `${PETAVUE_DOMAIN}/${page.settings?.slug || slugFor(page.name)}`}
    onClose={() => setSettingsOpen(false)}
    onSave={(name, meta) => {
      setMeta(page.id, name, meta);
      setSettingsOpen(false);
      toast.success(live ? "Settings saved. Publish the page again to put them live." : "Settings saved.");
    }}
  />
)}

A page with settings filled in

Password access, a meta description, the page hidden from search, its own Open Graph description, a Tag Manager ID, and forms sent to HubSpot and email.

const page = {
  name: "Q4 demo request page",
  meta: {
    title: "Book a Meridian demo",
    description: "See how Meridian forecasts your Q4 pipeline from CRM and ad data, and which deals will slip. …",
    sitemap: false,
    access: "password",
    password: "q4-partners",
    ogSameDescription: false,
    ogDescription: "A 30-minute demo of Meridian, with your own pipeline.",
    gtm: "GTM-5K2QX7P",
    forms: "both",
  },
  sections,
};

The groups

GroupFieldsNotes
GeneralPage nameA TextInput. An empty name is saved as the old name.
Access controlPublic, or Anyone with the passwordA full-width .lib-scope--fill switch. Password shows a Password field and a note with a lock icon.
SEO settingsSearch preview, Title tag, Meta description, Sitemap indexing, Page canonical URLThe title tag's placeholder is the page name. The toggle's hint changes with its state.
Open Graph settingsShare preview, title and description (each with "Same as SEO …"), imageThe title and description fields are disabled while their checkbox is ticked and show the SEO values.
TrackingPetavue tracking pixel, Google Tag Manager IDThe pixel is on by default.
FormsSend form submissions toA Dropdown: HubSpot, email to the workspace owner, or both.

Search preview

.lib-serp draws the result as a search engine would: the title in 16px medium --color-primary-800, the address in 12px green (#0B7A3B), and the description in 12px secondary text. The title is cut at 60 characters and the description at 155, with " …" added. With no description it says "Add a meta description. Without one, search engines pick text from the page."

Share preview

.lib-og draws a link card: a 236px image area, the title in 14px medium, the description cut at 200 characters, and the host in --color-grey-400. The image area shows the uploaded image, or the page's first two sections through ScaledPreview until one is uploaded. An uploaded image is read into a data URL and listed with its name and size in kB, with Replace and Delete.

What onSave gets

onSave(name, meta) is called with the page name (trimmed, or the old name when empty) and every setting:

KeyDefaultCleaned on save
titlethe page nametrimmed
description""trimmed
sitemaptrue
canonical""trimmed, trailing slashes removed
ogSameTitle, ogSameDescriptiontrue
ogTitle, ogDescription""
ogImagenull{ src, name, kb } once uploaded
access"public"
password""
pixeltrue
gtm""trimmed, uppercased
forms"hubspot"

Nothing is validated. A password page can be saved with an empty password, and any text is accepted as a canonical URL or Tag Manager ID.

Layout

PartStyle
PanelDialog at lg, 800px. The body scrolls past min(70vh, 720px).
Group headings12px uppercase semibold secondary text, with a 12px hint under some, and a --color-grey-100 line between groups.
Previews--color-grey-50 fill, 1px --color-grey-100 border, 8px corners.
Notes.lib-publish__note--icon: 12px text on --color-grey-50 with a 15px icon.
FooterClose and Save at 32px.

Props

PropTypeDefaultNotes
pagepage objectRequired. Reads name, meta and sections (the first two draw the share image).
addressstringRequired. The address without https://, such as go.meridian.com/q4-demo. Shown in the search preview; its host is shown in the share preview.
onSave(name, meta) => voidSave. Does not close the dialog.
onClose() => voidEscape, scrim click, the close icon and Close.

address has no fallback. Leaving it out throws, because the dialog calls address.split("/") to find the host.

Accessibility

  • The wrapper has role="dialog", aria-modal="true" and aria-label="Page settings". The visible title is "Q4 demo request page settings".
  • The access switch is two aria-pressed buttons in a group labelled "Restrict access".
  • The search and share previews are divs with an aria-label, but no role, so most screen readers ignore the label. They repeat what is in the fields.
  • The file input for the image is hidden and opened by the Upload button. It has aria-label="Open Graph image file".
  • Nothing is focused when the dialog opens. Focus is not kept inside and not returned to "Page settings" on close.

Do and don't

  • Do add new page options here, in the group they belong to, and add a row to the publish dialog's "Goes live with" if people should see it before publishing.
  • Do keep the previews next to the fields they reflect, so people see the effect while typing.
  • Don't add where the page is published here. That is the publish dialog's decision.
  • Don't save a live page's settings straight to the live page. They wait for the next publish.

When to use it

Use it for landing pages from the page builder. For settings on other objects, such as a workflow or a dashboard, use a Dialog from @/ui with the same group layout, or a settings page if there are more than a few groups.

Where it is used

FileWhat
src/pages/library/PageBuilderPage.jsxOpened by "Page settings" in the editor header, and by "Change in page settings" in the publish dialog. Saves through setMeta in usePagesStore.js.
src/pages/library/PageCode.jsxNot a caller, but the code view writes these settings into index.html and page.config.json. See Code view.

On this page