New projects: no Tailwind, no CSS framework, no page CSS. A project built on this library does not load Tailwind or other UI kits. Layout comes from ab-stack, ab-grid, ab-cluster, ab-split, ab-card; spacing from gap="1".."8". If you need a <style> block for anything other than :root { --ab-*: … }, you are probably missing a component.
Existing projects keep their styling. A project that already has a look (Tailwind, its own CSS) is never migrated unless the user asks. Its new parts may use ab-* components next to the existing markup: add only the <script type="module" src="https://design.abstra.io/v0/abstra-ds.js"> to its base.html (not the CSS file, whose global base styles would restyle the existing pages) and map the project's theme in :root: --ab-accent to its accent color, --ab-font-sans to its font. Style ab-* components only through attributes and --ab-* tokens, never utility classes on their insides.
Theme only through --ab-* tokens in one :root block in base.html. Brand: take main_color → --ab-accent, font_family → --ab-font-sans (load the font), logo_url → <ab-app brand-logo>. Never invent brand colors.
Data from Python goes into components as JS properties, never as HTML strings: table.rows = await get_rows(). HTML attributes are kebab-case and JS properties camelCase (empty-text ↔ table.emptyText); every component's API table lists both. Arrays and objects (rows, columns, items, data) are set as properties, or as JSON in the attribute from Jinja. Setting properties before a component has loaded is safe; set them again to update; null values show "—". Values are always rendered as text (no XSS). Never build markup with innerHTML + template strings.
Call Python with the generated functions or abCall("fn_name", {params}). abCall speaks the same protocol, works before the generated globals exist, and throws an Error with the server message.
Load data with abLoad(component, () => python_fn(args)), never with hand-written loading/try/catch. It sets loading (data already on screen stays visible), clears and shows error with a readable message (tables and charts get "Tentar de novo", which re-runs the same call), falls back to a toast for components without an error state, logs technical details to the console, and drops stale responses when a newer load starts (fast filter changes). The data goes to the component's data property (rows, data, value, items, code); an ab-table also accepts { rows, total, page } from a paginated Python function. One call for several components: abLoad([kpi1, kpi2, table], get_overview, { apply: (data, el) => … }). Errors from actions (save, delete): abToast(await abErrorMessage(error), { tone: "danger" }) inside an async handler. The public abErrorMessage helper returns Promise<string>; always await it before assigning text or showing a toast, even after the library has loaded. Rely on the built-in empty state (empty-text); never leave a blank panel.
Never use alert(), confirm() or prompt(). Use await abConfirm({ title, message, tone: "danger" }), abToast("Salvo"), <ab-alert> or error="…" on a field.
Tables paginate on the server: select(limit=, offset=) + count(); set total, listen to ab-page-change. Use client-sort only for small, fully-loaded lists.
Formatting is built in: column/kpi type = currency (BRL), number, percent (ratio 0.12 → 12%), date ("YYYY-MM-DD" is a local date), datetime. Don't pre-format numbers in Python.
Status = ab-badge with a tone (ok, warn, danger, info, accent, neutral); in tables use type: "badge", tones: { Pago: "ok" }.
Layout follows the task (see recipes): Overview, Work queue, Record, Report, Form, Process. At most 4 KPIs, only numbers people track.
Icons: <ab-icon name="…"> or the icon attribute of components. Built-in names (house, magnifying-glass, listed in catalog.json icons) render instantly; any other icon uses set:name, loaded on demand: lucide:camera, mdi:bank, ph:house-fill, fa6-brands:github, simple-icons:nubank. Sets: ph, lucide, tabler, mdi, heroicons, ri, bi, fa6-solid, fa6-regular, fa6-brands, carbon, fluent, simple-icons (list: https://design.abstra.io/icons/sets.json; names per set: /icons/<set>/index.json; search: https://design.abstra.io/components/icons/). Prefer one set per page. Only names that exist (a wrong name renders nothing and warns). No emoji icons.
Check the console after run_page: misuse is reported as [abstra-ds] <ab-…> … warnings. Fix all of them.
Lazy components: only the core (layout, shell, buttons, fields, table, dialog, tabs, KPI, feedback) is in abstra-ds.js; every other ab-* loads automatically the first time its tag is in the page. Setting properties is always safe (chart.data = rows works before it loads), but before calling a method at page load (upload.setStatus(), drawer.show(), tour.start()…) do await customElements.whenDefined("ab-drawer"). In event handlers (clicks) it is already loaded.
htmx works out of the box (htmx 2 and 4). ab-* form controls are native form fields, so <form hx-post> / hx-include send their values (ab-date-range sends <name>_from and <name>_to; multi-value controls repeat the name). Fragments returned by the server can contain any ab-* tag, including lazy ones. For components that take data (ab-table columns/rows/row-actions, ab-chart data, ab-tree data, ab-steps items, ab-transfer data, ab-cascader options, ab-command items…) render it as a JSON attribute from Jinja: rows='{{ rows|tojson }}' (single quotes outside). Component events are valid hx-triggers and their detail is sent as parameters automatically: hx-trigger="ab-page-change, ab-sort-change" sends page, pageSize / sort, dir; ab-filter-change sends each filter (<name>_from/<name>_to for date ranges); ab-tab-change sends value. Built in when htmx is present: the ab-button that triggered a request shows its loading state, hx-confirm opens abConfirm (never the browser's confirm), and HTTP errors show an abToast (turn off with <meta name="ab-htmx-errors" content="off"> if the page shows its own error). See the "Report com htmx" recipe.
Spacing and nesting: siblings always sit in an ab-stack, ab-grid or ab-cluster (never two blocks in a plain <div> or <form>: they touch). slot="…" works only on a component's direct children: a <form> with a card wraps the card (<form><ab-stack><ab-card>…</ab-card><ab-cluster justify="end">…submit…</ab-cluster></ab-stack></form>), never the other way round with slot="footer" inside the form. No card in a card: ab-kpi, ab-table and ab-chart go directly in the page; inside an ab-card use ab-statistic for a number. The console warns about misplaced slots and KPIs inside cards.
Diagrams: ab-diagram with ab-node children (any content) and ab-edge from to (or parent on the node): layout="tree" for org charts and group structures (collapsible, edge-label), layout="layered" for flowcharts, processes and data models (shape="diamond"/"circle", data-port + from-port/to-port, marker-end="many"), layout="manual" + draggable for free canvases. Never draw diagrams by hand in SVG.
Language and direction follow <html lang dir>. Built-in texts (pagination, empty/error states, dialog buttons, calendar, upload…) are Brazilian Portuguese for pt* or no lang, English for any other language; numbers and dates format with the real lang. For another language, put the page's texts in <script type="application/json" id="ab-strings">{ "next": "Weiter", "pageOf": "Seite {page} von {pages}", "items": { "one": "{n} Eintrag", "other": "{n} Einträge" }, "date": { "today": "Heute" } }</script> in <head> (or window.abStrings = {…}, or abSetStrings({…}) at any time): partial is fine, missing keys fall back to English. Values go in {name} placeholders, plurals as { one, other, … } objects (Intl.PluralRules), no functions. Every key with its placeholders: https://design.abstra.io/strings/en.json (await abStringsTemplate() in JS). With dir="rtl" layout, directional icons (caret-left, arrow-right, sign-out…), floating panels, submenus, arrow keys, carousels and toasts mirror; <ab-icon no-flip> opts out; ab-drawer placement="start|end" follows the direction. The date picker grid is always Gregorian; other dates use the language's default calendar (Persian for fa): lang="fa-u-ca-gregory" makes them Gregorian. Not mirrored yet: ab-slider, ab-spreadsheet, ab-diagram, chart axes. Details: https://design.abstra.io/i18n/