Product guide · CSS and JavaScript API
ZZE Brand Scripts
Brand Scripts is the shared front-end framework for ZZE web projects. New web projects should use its stylesheet, design tokens, components, and browser helpers as their common foundation. The files are static assets: link the stylesheet and import JavaScript modules directly, with no package installation or build step.
This service hosts shared front-end assets. The api() helper sends requests to the consuming app's own origin under /v1; configure that app's server or proxy to provide those endpoints.
Quick start
Add the stylesheet in the page head and load your application entry point as an ES module. Replace the host with the public host for your deployment if it differs.
<link rel="stylesheet" href="https://bscripts.zzent.org/css/app.css">
<div id="app"></div>
<div id="toasts"></div>
<script type="module">
import { h, toast } from "https://bscripts.zzent.org/js/app.js";
document.querySelector("#app").append(
h("button", { class: "primary", onclick: () => toast("Ready") }, "Continue")
);
</script>
Keep the #toasts element in the page if you use toast() or fail(). Module imports work when the consuming page is served over HTTP(S); opening a local file directly may be blocked by browser module security rules.
DOM helper
h(tag, attrs, ...children)
Creates an element, applies attributes and event handlers, then appends children. Nested child arrays are flattened. Strings and numbers become text nodes, and null, undefined, and false children are skipped.
import { h } from "https://bscripts.zzent.org/js/app.js";
const card = h("section", { class: "card p-2" },
h("h2", {}, "Account"),
h("button", { class: "primary", onclick: saveAccount }, "Save")
);
document.querySelector("#app").append(card);
Attribute handling: class sets className; value sets the element value; true creates a boolean attribute; false, null, and undefined are omitted. Attribute names beginning with on register event listeners, so use a function value such as onclick: handler.
Text children are added with textContent-equivalent text nodes. For dynamic markup, prefer composing elements with h() instead of inserting untrusted HTML strings.
CSS reference
Include /css/app.css. The stylesheet defines color tokens, responsive layout styles, form controls, tables, feedback elements, and spacing utilities. Your app can override the tokens in its own stylesheet.
Theme tokens
| Token | Purpose |
|---|---|
--bg | Page and subtle surface background |
--card | Card, control, and table background |
--ink, --muted | Main and secondary text |
--line | Borders and dividers |
--brand, --brand-ink | Primary action background and foreground |
--danger, --warn, --ok | Error, warning, and success states |
--radius | Shared corner radius |
The stylesheet includes light defaults and a dark palette under prefers-color-scheme: dark. Define these custom properties on :root in your app to customize the theme.
Layout and components
| Class / element | Usage |
|---|---|
.layout | Two-column app shell with a 220px navigation column; stacks on narrow screens. |
nav, nav a, .on, .brand, .who | Navigation panel and common active, brand, and user details. |
main, h1, h2 | Main content spacing and heading sizes. |
.bar, .sp | Flexible toolbar; the spacer pushes following controls to the end. |
.card, .tiles, .tile | Surfaces and responsive metric tiles. |
.wrap, table, th, td, tr.click | Scrollable data tables, row styling, and hover affordance. |
button, .primary, .danger, .sm | Button styles and variants. |
input, select, textarea, label, .chk | Form controls and checkbox labels. |
.badge, .b-ok, .b-bad, .b-warn | Status pills and state colors. |
.modal-bg, .modal, .wide, .foot | Modal overlay, panel, wide variant, and action row. |
.grid2, .login, .pager, .tree-child | Two-column form grid, login panel, pagination controls, and indented tree content. |
.muted, .right | Secondary text and right-aligned content. |
Spacing utilities
Padding classes: p-0, p-1, p-2, pl-1, pl-2, pr-1, pr-2, pt-1, pt-2, pb-1, pb-2, pb-3. Margin classes: m-0 through m-3, mt-1 through mt-3, and mb-1 through mb-3. Numbered steps are approximately 4–5px each; .bg-primary applies the brand background and foreground colors.
Session storage
session
A small wrapper around browser local storage. It uses the keys ep_token and ep_user, and safely handles storage errors.
import { session } from "https://bscripts.zzent.org/js/app.js";
session.set(accessToken, { id: "u-123", name: "Alex" });
console.log(session.token); // access token or null
console.log(session.user); // parsed user object or null
session.clear();
Do not store sensitive information in user; local storage is accessible to JavaScript running on the same origin. The API helper uses the stored token as a Bearer token.
API requests and errors
api(method, path, { body, query } = {})
Sends a fetch request to /v1 plus the supplied path, adds JSON request headers when a body is supplied, adds Authorization: Bearer … when a session token exists, and returns parsed JSON (or text if the response is not JSON).
const result = await api("GET", "/orders", {
query: { page: 1, status: "PAID", search: "" }
});
await api("POST", "/orders", {
body: { customerId: "c-42", total: 1250 }
});
Query entries with undefined, null, or empty-string values are omitted. If a response is 401 while a token is present, the helper clears the session and reloads the page. Other non-success responses throw ApiError.
ApiError
Extends Error and exposes status, code, and details. The message comes from body.error.message when present, otherwise it is Request failed (STATUS).
The request URL is relative to the consuming page's origin, for example https://your-app.example/v1/orders. It is not sent to the brand scripts host. Implement or proxy the API routes on the consuming app's origin.
UI components
toast(message, error = false) and fail(error)
Shows a notice in the element with id toasts. Normal notices disappear after 3 seconds; error notices after 6 seconds. fail() logs the error and shows its message as an error notice.
document.body.append(h("div", { id: "toasts" }));
toast("Saved successfully");
fail(new Error("Could not save"));
badge(status)
Returns a status-pill element. Paid, delivered, confirmed, sent, approved, and refunded values use b-ok; failed, cancelled, refund-due, returned, and rejected values use b-bad; other states use b-warn. Underscores are displayed as spaces.
table(columns, rows, options)
Builds a responsive table element. Each column has a title and either a key or a render(row) function. Set right: true for right alignment. Options include onRow(row) to make rows clickable and empty to customize the no-rows message.
const view = table([
{ title: "Order", key: "id" },
{ title: "State", render: row => badge(row.status) },
{ title: "Amount", right: true, render: row => le(row.total) }
], orders, { onRow: openOrder, empty: "No orders yet" });
pager(state, total, reload)
Creates previous/next controls. Pass a mutable state object with page and limit, the total row count, and a callback that reloads the current page. The helper updates state.page before calling the callback.
const pageState = { page: 1, limit: 20 };
const controls = pager(pageState, totalOrders, loadOrders);
modal(title, body, options)
Appends a modal to the document body and returns { close, el }. Options: wide widens the panel, actions supplies footer elements, and sticky prevents closing by clicking the overlay. The close button always remains available.
const dialog = modal("Order details",
h("p", {}, "Review this order before continuing."),
{ wide: true, actions: [h("button", {}, "Done")] }
);
// Close it programmatically: dialog.close();
formModal(title, fields, onSubmit, options)
Builds a modal form. Each field supports name, label, type, value, required, and hint. Supported types are text (default), number, textarea, select, checkbox, password, and datetime-local. Select fields accept options: [[value, label], ...]. Set nullable: true to submit null for an empty text, textarea, or select field. Number values become numbers; datetime-local values become ISO timestamps. The async submit callback receives a values object. Return or resolve to close the dialog; throw/reject to keep it open and show the error. Set { submitLabel: "Create" } to change the submit button text.
formModal("Invite teammate", [
{ name: "email", label: "Email", required: true },
{ name: "role", label: "Role", type: "select",
options: [["viewer", "Viewer"], ["admin", "Admin"]] }
], async values => {
await api("POST", "/invitations", { body: values });
}, { submitLabel: "Send invite" });
confirmBox(message)
Wraps the browser's window.confirm() and returns its boolean result.
Formatting helpers
| Helper | Behavior |
|---|---|
le(value) | Formats a number as an Le amount with up to two decimals; null and undefined become an empty string. |
dt(value) | Formats a date value using the browser locale with medium date and short time; empty input becomes an empty string. |
Common pattern
Combine the DOM helper, API helper, table renderer, and error toast for a simple data screen.
import { h, api, table, badge, fail } from
"https://bscripts.zzent.org/js/app.js";
async function loadOrders() {
const target = document.querySelector("#orders");
target.textContent = "Loading…";
try {
const { items } = await api("GET", "/orders");
target.replaceChildren(table([
{ title: "Order", key: "id" },
{ title: "Status", render: row => badge(row.status) }
], items));
} catch (error) {
target.textContent = "Could not load orders.";
fail(error);
}
}
document.querySelector("#app").append(
h("main", {}, h("h1", {}, "Orders"), h("div", { id: "orders" }))
);
loadOrders();
Hosting and deployment
The Docker image serves the static files with NGINX. The supplied Compose configuration publishes container port 80 on host port 8001. After deployment, assets are available at /css/app.css and /js/app.js. Update references to match your public hostname or reverse-proxy configuration.
Cross-origin asset requests require the asset server's CORS allowlist to include the consuming site origin. The included NGINX configuration currently allows http://localhost:8002; add your production app origin to that configuration when loading the module from another origin. For the API helper, CORS is governed by the consuming API server because the request is made to the consuming app's own origin.
See the homepage for the quick examples and live previews.