- Import macros
- Partials
- Assets
- App chrome
- Overriding templates (REQ-TWIG-001)
- Override vs upgrade
- Remap CSS tokens
- Feature bundles
- Kitchen sink
- Related docs
{% import '@NowoUiKitBundle/macros/ui.html.twig' as ui %}
<button class="{{ ui.btn('primary') }}">Primary</button>
<button class="{{ ui.btn('danger', 'sm') }}">Delete</button>
{# Pass a framework override (feature bundle’s own css_framework): #}
<button class="{{ ui.btn('primary', null, 'tailwind') }}">Tailwind</button>
<div class="{{ ui.toolbar() }}">…</div>
<div class="{{ ui.table_wrap() }}"><table class="{{ ui.table() }}">…</table></div>
<span class="{{ ui.badge('success') }}">OK</span>
<div class="{{ ui.card() }}">…</div>
<div class="{{ ui.progress() }}"><div class="{{ ui.progress_bar() }}" style="width:40%"></div></div>
<span class="{{ ui.spinner('sm') }}"></span>Macros always emit semantic nowo-ui-* classes. Named stacks also emit Bootstrap / Tailwind / Foundation classes. With custom or none, only nowo-ui-* remain.
ui.badge() keeps BC: ui.badge('tailwind') is still a framework override; ui.badge('success') / ui.badge('danger', 'bootstrap5') set variants.
Twig globals (from config):
nowo_ui_kit_css_frameworknowo_ui_kit_icon_setnowo_ui_kit_row_actions_display
| Subpath | Purpose |
|---|---|
partials/_pagination.html.twig |
Server-side pagination |
partials/_empty.html.twig |
Empty state |
partials/_flashes.html.twig |
Inline Symfony flashes |
partials/_toasts.html.twig |
Fixed toast stack from flashes (string or {title, message}) |
partials/_row_actions.html.twig |
Edit / delete / view / copy cluster (display: icon | text | icon_text) |
partials/_page_header.html.twig |
Title + intro + toolbar HTML |
partials/_tabs.html.twig |
Tab link nav (no JS). In-page panels: nowo-ui-tabs.js / Stimulus tabs peer |
partials/_filters.html.twig |
Search + apply/clear + actions slot |
partials/_card.html.twig |
Card / panel (header/body/footer) |
partials/_modal_shell.html.twig |
Modal shell |
partials/_confirm.html.twig |
Confirm <dialog> (host owns POST+CSRF) |
partials/_page_loader.html.twig |
Full-page loader overlay (visual: spinner|orb) |
partials/_thinking_orb.html.twig |
Thinking orb canvas (data-nowo-ui-orb) |
partials/_brand.html.twig |
Brand mark (text / img / SVG) |
partials/_theme_toggle.html.twig |
Light/dark toggle |
partials/_width_toggle.html.twig |
Main full width ↔ content toggle |
partials/_locale_switcher.html.twig |
Locale menu (host supplies URLs/forms) |
partials/_kebab.html.twig |
Overflow / kebab menu |
partials/_burger.html.twig |
Burger / aside toggle |
partials/_avatar.html.twig |
Avatar initials or image |
partials/_aside.html.twig |
Left sidebar (flat or nested children) |
partials/_aside_nav_items.html.twig |
Recursive nav items (internal) |
partials/_user_menu.html.twig |
Avatar dropdown + links + logout |
partials/_footer.html.twig |
Site footer |
partials/_shell.html.twig |
Composed chrome |
components/_icon.html.twig |
Icon by icon_set |
macros/ui.html.twig |
Class macros |
demo/kitchen_sink.html.twig |
Full component showcase |
Example:
{% include '@NowoUiKitBundle/partials/_pagination.html.twig' with {
pagination: { page: 1, per_page: 20, total: 100, total_pages: 5 },
item_count: 20,
route: 'app_items',
route_params: {},
query: { q: app.request.query.get('q') }
} %}Row actions (inherits nowo_ui_kit_row_actions_display; override with display). Full contract: ROW_ACTIONS.md.
{% include '@NowoUiKitBundle/partials/_row_actions.html.twig' with {
actions: [
{ kind: 'view', href: path('app_item_show', { id: item.id }) },
{ kind: 'edit', href: path('app_item_edit', { id: item.id }) },
{ kind: 'delete', tag: 'button', confirm_id: 'delete-' ~ item.id }
]
} %}
{# Visible labels instead of icon-only #}
{% include '@NowoUiKitBundle/partials/_row_actions.html.twig' with {
display: 'text',
actions: [
{ kind: 'edit', href: path('app_item_edit', { id: item.id }) },
{
kind: 'delete',
method: 'POST',
href: path('app_item_delete', { id: item.id }),
csrf_token: csrf_token('delete' ~ item.id)
}
]
} %}Confirm trigger:
<button type="button" class="{{ ui.btn('danger') }}"
{{ ui.confirm_toggle_attrs() }} {{ ui.confirm_target_attr('delete-item') }}>
Delete
</button>
{% include '@NowoUiKitBundle/partials/_confirm.html.twig' with {
id: 'delete-item',
title: 'Delete?',
body: '<p>Irreversible.</p>',
footer: confirm_footer_html
} %}Named package nowo_ui_kit (REQ-ASSETS-004), base_path /bundles/nowouikit.
Sources are TypeScript under src/Resources/assets/src/, built with Vite + pnpm to src/Resources/public/js/*.js (REQ-ASSETS-001):
pnpm install
pnpm run build<link rel="stylesheet" href="{{ asset('css/nowo-ui.css', 'nowo_ui_kit') }}">
<script src="{{ asset('js/nowo-ui-modal.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-shell.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-toast.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-confirm.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-page-loader.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-clipboard.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-tabs.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-theme.js', 'nowo_ui_kit') }}" defer></script>
<script src="{{ asset('js/nowo-ui-orb.js', 'nowo_ui_kit') }}" defer></script>Run php bin/console assets:install after install/update.
nowo-ui-modal.js handles data-nowo-modal-open / data-nowo-modal-close for custom / none / tailwind. Bootstrap stacks use data-bs-* and do not require this script for open/close.
nowo-ui-shell.js toggles the left aside via data-nowo-ui-burger, nested groups via data-nowo-ui-nav-group-toggle, and main width via data-nowo-ui-width-toggle.
nowo-ui-clipboard.js copies via data-nowo-ui-clipboard* (text or same-origin URL). nowo-ui-tabs.js switches in-page panels via data-nowo-ui-tabs* (Twig _tabs remains link navigation).
Optional Stimulus peers: src/Resources/assets/stimulus-peers/ — see STIMULUS.md.
nowo-ui-orb.js mounts Thinking Orbs on canvas[data-nowo-ui-orb] (local MIT canvas engine; no CDN). Use _thinking_orb.html.twig or _page_loader.html.twig with visual: 'orb'.
{% include '@NowoUiKitBundle/partials/_shell.html.twig' with {
brand: 'My App',
aside_items: [
{ label: 'Dashboard', href: path('app_home'), current: true },
{
label: 'Settings',
open: true,
children: [
{ label: 'General', href: path('app_settings') }
]
}
],
name: app.user.userIdentifier,
initials: 'AB',
user_links: [
{ label: 'Profile', href: path('app_profile') }
],
logout_href: path('app_logout'),
footer_links: [
{ label: 'Privacy', href: path('legal_privacy') }
],
footer_copyright: '© 2026',
main: page_html
} %}Or include _burger, _aside, _avatar, _user_menu, and _footer individually. Pass header_end for theme/locale toggles.
Place a file at:
templates/bundles/NowoUiKitBundle/<subpath>
Application overrides always win. Twig namespace: NowoUiKitBundle.
| Depth | How | On package bump |
|---|---|---|
| Preferred | Remap --nowo-ui-* CSS tokens; keep macros |
Vendor UI upgrades apply |
| Surgical | Override one partial (_row_actions, _pagination, …) |
Only that file is frozen |
| Full fork | Copy a whole template under templates/bundles/… |
That path stays frozen until you delete/merge it |
:root, .kit-admin {
--nowo-ui-primary: #1f6f54;
--nowo-ui-danger: #c0392b;
--nowo-ui-surface: #fff;
--nowo-ui-text: #0c1210;
/* …see nowo-ui.css for full token list */
}
/* Dark: kit ships defaults under [data-theme="dark"]; remap as needed */Other Nowo admin UIs should import @NowoUiKitBundle/macros/ui.html.twig and include partials instead of shipping a private _ui_macros / nowo-ui.css copy.
Align nowo_ui_kit.css_framework with the feature’s css_framework, or pass the feature framework as the macros’ trailing framework argument.
See ADOPTION.md.
Render @NowoUiKitBundle/demo/kitchen_sink.html.twig from a host controller (see demo/symfony8). Optional context: pagination, layout_template.
- STIMULUS.md — data-attribute contracts / Stimulus peers
- ADOPTION.md — consolidating Beacon + feature bundles
- ROADMAP.md — phase status