Skip to content

Customizing the look

The interface is built on daisyUI and Tailwind, drawn on the server with Jinja, with HTMX for partial updates and Alpine for the few pieces that keep state. The CSS and JavaScript ship inside the package, so nothing needs Node to install or run.

Override a template

Point the admin at a folder of your own. A template found there is used instead of the one that ships:

admin = Admin(engine, views=[OrderView], template_dirs=["templates/admin"])

Keep the same relative path, so templates/admin/adminsite/login.html replaces the sign in page.

Template What it draws
adminsite/base.html The shell: sidebar, header, messages
adminsite/index.html The front page
adminsite/list.html, _toolbar.html, _table.html The list, its search and filters, and the table
adminsite/_filters.html, _filter_chips.html The filters drawer, and the chips of the filters in use
adminsite/_bulk_actions.html The Actions menu for the ticked rows
adminsite/_saved_tabs.html, _row_actions.html The saved views above a list, and a row's menu
adminsite/detail.html, _history.html One record, and its history
adminsite/form.html, _field.html, _inlines.html The create and edit form, and its child rows
adminsite/dashboard/*.html The overview's cards
adminsite/_icons.html, _values.html The icons, and how a status or a yes and no is drawn
adminsite/_empty.html An empty value
adminsite/widgets/*.html One form control each
adminsite/activity.html The activity page
adminsite/login.html Signing in

A banner on every page

banner puts a line of text above every page, the sign in page included, such as which copy of the admin people are looking at:

admin = Admin(engine, banner="Staging: changes here are not real.")

The text is escaped. Give it as Html(...) to include a link.

Draw a widget your way

Each form control is a small template under adminsite/widgets/, named after the field's widget: input.html, textarea.html, checkbox.html, select.html, relation.html and readonly.html. To draw one your way, add a file with that name. To add a new one, give your field a new widget name and add its template:

class ColourField(StringField):
    widget = "colour"
{# templates/admin/adminsite/widgets/colour.html #}
<input type="color" class="input w-24" id="field-{{ row.path }}"
       name="{{ row.path }}" value="{{ row.value or '#3ecf8e' }}">

A widget template gets row, which has path, value, field, choices, error and required. A widget with no template of its own uses input.html.

Colours and theme

There is a light theme and a dark one, and the admin follows the browser unless the user picks one. To change the colours, edit the theme in frontend/input.css and rebuild:

cd frontend
npm install
npm run build

Beside daisyUI's own colours, the themes set CSS variables a stylesheet of yours can change: --subtle for the band that holds a card's title, --line and --line-strong between rows, --muted for text that steps back, --link, --selected for ticked rows, --chart and --chart-strong for bars, and --field, --field-border and --field-disabled for inputs. Text comes in four sizes, --text-caption, --text-note, --text-label and --text-body, in the system's own typeface. Badges use six tones, as Badge colours shows.

The build writes src/adminsite/static/adminsite.css. If your own templates use Tailwind or daisyUI classes the shipped stylesheet does not have, build a stylesheet of your own from the same input, with your template folder added as a @source, and serve it from your own base.html.

Compact mode

Each person can turn on Compact mode in the account menu, beside Dark mode. Lists then draw their rows 36px apart instead of 48, with values a size smaller, so a screen holds about half as many rows again, and record pages draw their fields closer together. Forms keep their spacing, and the choice stays in that person's browser.

A list's sizes are CSS variables a stylesheet of yours can change: --list-row, --list-head, --list-cell, --list-text, --list-picture and --list-cut, set on :root and again under [data-density="compact"].