Skip to main content

Documentation

No results found.
Features

Design Library

WebProCMS ships with a built-in design library — a catalogue of pre-designed rows organised by category that editors can drop into any page from the page editor's "Add row" drawer. Every row is built from a small set of editable B...

WebProCMS ships with a built-in design library — a catalogue of pre-designed rows organised by category that editors can drop into any page from the page editor's "Add row" drawer. Every row is built from a small set of editable Blade primitives (<x-dl.*> components), so the moment a row lands on a page, every text string, image, button label, padding token, and Tailwind class is editable through the standard sidebar UI. There is no "static" content in a design library row.


The problem

A CMS that ships only one or two starter templates leaves editors stuck — every new page is a manual layout job. A CMS that ships hundreds of static templates with hardcoded copy and styling leaves editors stuck in a different way: any deviation from the template means hand-editing the resulting HTML, and the template gallery becomes a graveyard of "almost what I want" options.

WebProCMS picks a third path. The design library is a curated catalogue — enough rows to cover every common section a marketing site needs (heroes, features grids, pricing tables, testimonials, FAQs, footers, etc.), but every single row is built from the same handful of editable primitives, so the gap between "library row" and "what the user actually wants" is always closable through the sidebar.

The fix

Two pickers, one shared component vocabulary:

  • The add-row picker opens a drawer of full sections grouped by category, with live preview thumbnails. Filtering by category narrows the list; a search box matches by row name.
  • The add-item picker is a smaller modal that opens from the + button between items inside a row. It lists insertable primitives — heading, subheadline, image, video, button, layout containers, repeater widgets — that drop in alongside whatever's already there.

Rows from the first picker are full pre-styled sections; items from the second picker are unstyled or minimally-styled snippets meant to be customised after insert.

Categories

Rows live in resources/design-library/rows/ under one directory per top-level category (RowCategory); three of those — Page Content, Page Layouts, and System — group further into sub-categories (RowSubCategory) that show up as a nested directory level and a secondary filter. The current set:

Category What's in it
hero Page heroes — headline + subhead + CTA + optional image/video
below-hero Rows designed to sit directly under a hero (stat bars, feature strips)
page-title Subpage banner titles with breadcrumbs
header Site headers — clean, dark, centered, transparent, pill, and ecommerce variants
footer Site footers — minimal, multi-column, with newsletter signup, with social links
page-content General content rows, grouped into sub-categories: content, CTA, countdown, features, FAQs, gallery, icon-list, logos, pricing, slider, social-proof, team
page-layouts Full-page list/detail layout variants shared across every record system (blog, content types)
system Auth + error pages: login, register, forgot-password, reset-password, not-found
contact Contact forms, contact info blocks, map embeds
popups Page-embedded modal/popup rows
campaign-popups / campaign-bars Popup and announcement-bar designs for the Popups & Announcement Bars feature
e-commerce Product grids, single product, cart-related rows
donations Donation form and campaign rows (Donations feature)
real-estate Listing search/detail rows (Real Estate feature)
restaurant Menu and ordering rows (Restaurant feature)
booking Appointment booking rows (Online Booking feature)
social Social feed rows (Social Feed feature)
events Events calendar rows (Events feature)
courses Course catalog/detail rows (Courses feature)

The list is data-driven: any new .blade.php file dropped into a category (or sub-category) folder is picked up by php artisan design-library:index and appears in the picker.

Filtering and previews

The add-row drawer surfaces every category as a chip filter at the top. The current page's context (header zone vs. body zone vs. footer zone) restricts which categories are offered — a header row picker won't show pricing tables, a body row picker won't show site headers.

Each row tile shows a live thumbnail rendered through Tailwind's browser CDN so previews always reflect the current brand colors and theme tokens. Hovering the tile shows the full row name; clicking it inserts the row at the position the user clicked from.

All classes are editable: the <x-dl.*> vocabulary

Every row in the design library is built from a small set of components in resources/views/components/dl/:

Component Purpose
<x-dl.section> The required outer wrapper — registers section_classes, section_container_classes, section_id, section_attrs. Every row starts with this.
<x-dl.heading> Toggleable heading with selectable h1–h4 tag
<x-dl.subheadline> Toggleable supporting text
<x-dl.buttons> Primary + secondary CTA pair with editable labels and classes
<x-dl.media>, <x-dl.image> Image with picker, alt, wrapper classes
<x-dl.video> Video with three source modes — self-hosted (media-library file), adaptive (HLS/DASH URL), or YouTube/Vimeo embed. Self-hosted/adaptive posters come from the video's media-library item
<x-dl.link> Inline link with toggle, label, URL, new-tab
<x-dl.icon> Heroicon (outline or solid) with optional wrapper
<x-dl.wrapper>, <x-dl.loop-item>, <x-dl.group> Generic editable element wrappers
<x-dl.repeater>, <x-dl.gallery>, <x-dl.slider>, <x-dl.accordion> List-driven widgets with add/edit/remove items

Because every visible class is registered as an editable field on one of these components, there's no "this row's footer is hardcoded" or "you can't change the icon size on a features grid" — the sidebar exposes everything. A row template is just a particular arrangement of these components with a particular set of default classes.

Brand-aware colors

Design library rows never hardcode hex values or vendor color names like text-blue-500. Every color reference uses one of three semantic families — primary, secondary, or tone — each with a 19-shade palette. Branding settings then map those families to the install's actual brand colors.

The result: dropping a row into a fresh install picks up the correct brand colors automatically, and changing a brand color in settings updates every row on the site at once.

See the design-library skill's styling-rules reference and the "Branding Color System" section in CLAUDE.md for the rules.

The add-item picker

Once a row is on the page, the + button between any two items opens the add-item picker. It groups primitives into three sections:

Group Items
Basic Heading, Subheadline, Rich Text, Button, Buttons (pair), Link, Image, Video, Icon
Layout Container, Block, Div, Panel, Grid (with column-layout chooser), Grid Item, Card
Widgets Accordion, Gallery, Slider, Form

Items are context-aware. Inserting inside a Grid restricts the picker to grid-compatible items (Grid Item, Card); inserting outside a grid hides those and shows the rest. The mapping lives in RowItemLibrary::itemsForContainer.

The Grid item triggers a follow-up modal — a column-layout chooser with ten preset shapes (2/3/4/5/6 even columns, plus 1/2, 2/1, 1/3, 3/1, and 1/2/1 splits). The picked layout determines the grid's responsive Tailwind classes and the per-item column spans.

Replace template ("browse mode")

Any row already on the page can be swapped for a different template without losing its section-level styling. From the row's design panel, clicking "Browse alternates" opens an inline carousel that cycles through every row in the same category. Each step replaces the row's blade with the new template, eager-wraps it in editor markers, and refreshes the preview.

What carries over: section background, padding, container width, custom IDs, sticky-section toggle — everything keyed under section_* plus toggle_sticky.

What gets dropped: component-level overrides (the old headline text, the old button label, the old card images). The new template's defaults take their place — the user is asking for a different design, not a different copy of the same content.

The user can also change category mid-browse and the carousel re-populates with rows from the new category. Implementation lives in EditorLibraryActions::applyBrowseRow.

Refresh from design library

A row's blade is copied into the page file at insert time, so subsequent updates to the source design library template don't automatically propagate. The discard menu's Refresh from design library action solves this:

  • Per-page — re-pull every non-shared row's blade from its source template, re-bake saved overrides on top
  • Per-row — same, but for one row only
  • Include shared rows — opt-in checkbox in the page-wide modal that also refreshes shared rows (which propagates to every page that includes them)

Refresh is non-destructive for saved overrides. The new template replaces the blade structure, then every active ContentOverride for the row is re-baked into the new file via BladeClassSyncer. Customisations survive the structural change. Implementation lives in EditorDiscardActions::refreshRowsFromDesignLibrary.

Push back to library

Designers iterating on a row often want their customisations to become the new default for future inserts of that template. The "Push to design library" button on the row's design panel writes the current class value of a field back to the source .blade.php file, so the next time someone adds the same row, they get the new defaults.

This only applies to classes-type fields — it's a way to refine library defaults from the editor, not to overwrite content. Shared rows and rows whose source template no longer exists hide the button. Implementation lives in EditorLibraryActions::pushClassesToLibrary.

Save your own rows (custom library)

The shipped library is a starting point — an install can build its own rows and reuse them across the site, no code required. There are two ways in, and both produce the same thing: a custom row that lives only on this install.

  • Save a section from the page editor. Build a section you like in the editor, open its ⋯ menu, and choose Save to library. A modal asks for a name and category (plus a sub-category and a "full-page design" option). A Keep content toggle decides what the saved row remembers: on, it captures the section exactly as built (text, images, colors); off, it saves only the layout and colors with placeholder copy, so the design is reusable without dragging your specific words along.
  • Save a whole page. The editor toolbar's ⋯ menu has Save whole page to library — it saves every section as a custom row and a reusable page bundle filed under a website type (SaaS, Service, Law, eCommerce, …) that shows up in the Page Library tab, where Use this page can scaffold a fresh page from it. (Shared rows like the site header are skipped — they're managed globally.)
  • New Row in the Design Library. Admins can also author a row directly from Dashboard → Design Library → New Row by writing the Blade markup, for cases where you want to start from scratch rather than from a live section.

Once saved, custom rows appear in the page editor's Insert Section drawer under a My rows filter, with Rename and Remove on each one. (The shipped rows stay read-only — you can't rename or delete what came with the CMS, only your own.) A custom full-page design tagged that way also feeds the Detail Layout dropdown for matching records.

Custom rows survive CMS updates. They're stored outside the files that ship with the CMS — in a separate, git-ignored tree — so a git pull update can never collide with them and never overwrites them. They're also captured in backups, which is their portability path between installs. Under the hood a custom row is just a normal library row with an is_custom flag; the editor serializer (LibraryRowExporter) turns a live section back into a reusable template, and EditorLibraryExportActions drives the save/rename flows.

The Page Library: whole-page designs by website type

Alongside single rows, the library catalogues page bundles — complete page designs described as an ordered list of library rows. The Page Library tab (Dashboard → Design Library → Page Library) shows them as preview cards filtered by website type (SaaS, Service, eCommerce, Law, Nonprofit, Healthcare, Custom), so a law firm browses law-firm pages, not everything.

  • Use this page. Every card has a Use this page button (admin): pick a page name and URL, and the bundle is materialized as a real, live page on your site — each section inserted exactly as if you'd added it from the drawer, route registered, ready in the editor. Your edits never touch the library copy.
  • Where bundles come from. The CMS ships a set (including every shipped theme's pages — see below), and Save whole page to library in the editor adds your own, filed under the website type you pick.
  • Bundles feed themes. A shipped theme's pages are compiled from Page Library bundles by themes:build — the bundle is the canonical source of that page design, so one fix there reaches the theme, the Page Library card, and every future "Use this page" insert. (Details in themes.md under "Compose-at-build".)

A bundle itself is a tiny frontmatter-only file — @name, @categories, and an ordered @rows list of row template names — under resources/design-library/pages/{website-type}/ (shipped) or the git-ignored custom tree (yours).

Adding a built-in row (developers)

To add a row to the shipped library — one that ships to every install and updates with the CMS — drop a file in via the file system:

  1. Drop a new {name}.blade.php file into a category folder under resources/design-library/rows/
  2. Add a frontmatter comment with @name, @description, and @sort
  3. Build the row using only <x-dl.*> components (every visible class must go through one)
  4. Run php artisan design-library:index to register it in the database

The row appears in the picker on the next editor load. No manual database insert is required. (For an install-specific row that should not ship upstream, use Save your own rows above instead — the dashboard/editor flows write to the git-ignored custom tree.)

The full author guide — frontmatter format, field-key naming conventions, the complete <x-dl.*> vocabulary, image performance rules, and color usage rules — lives in the "Design Library" section of CLAUDE.md.

Accessible interactive components

The interactive components in the library — sliders/carousels and accordions (FAQ rows and beyond) — ship with WCAG 2.1 AA-friendly keyboard support and ARIA semantics built in. Sliders keep inactive slides out of the keyboard tab order; accordions follow the WAI-ARIA accordion pattern (Up/Down/Home/End focus navigation, aria-expanded, role="region" panels). Because the behavior lives in the shared <x-dl.*> components rather than in each row template, every row built on them inherits it automatically — and any new slider or accordion row gets it for free.

See accessible-interactive-components.md for the full breakdown.

What lives where

Path Purpose
resources/design-library/rows/ Shipped row templates organised by category (update with the CMS).
resources/design-library/pages/ Shipped page bundles organised by website type (@rows manifests — the Page Library tab; theme pages compile from these).
resources/design-library-custom/ Install-authored custom rows + page bundles (git-ignored, merge-safe, is_custom).
app/Support/Pages/PageBundleMaterializer.php "Use this page" — materializes a bundle into a live page with fresh row slugs.
app/Support/Themes/ThemePageComposer.php themes:build — compiles bundles into a theme's frozen stub pages.
resources/design-library/items/ Insertable item snippets shown in the in-row + picker.
resources/views/components/dl/ The shared <x-dl.*> Blade components every row is built from.
app/View/Components/Dl/ The PHP classes for those components, including schemaFields() declarations.
app/Support/Rows/RowItemLibrary.php Catalogue of items + grid-layout presets for the add-item picker.
app/Concerns/EditorLibraryActions.php Add-row drawer, browse-mode (replace template), insert blank starter, push classes to library.
app/Concerns/EditorLibraryExportActions.php Save section / save whole page / rename custom rows from the editor.
app/Support/LibraryRowExporter.php Serializes a live section back into a reusable custom row template.
app/Models/DesignRow.php Row entry — name, category, source file, schema fields, is_custom.
app/Jobs/IndexDesignLibraryJob.php The job that scans both the shipped and custom trees and writes rows to the database.

For the broader page editor that hosts the library drawer, see page-editor-tour.md.