NexaAdmin documentation

How to make pages, set colours and themes, and use the tables, charts, forms and overlays.

Quick start

  1. Unzip the download and open index.html in your browser. Every page works straight from the folder.
  2. Browse the 16 pages from the sidebar. components.html shows every interface part on one page.
  3. Copy blank.html to start a page of your own.
  4. Set your brand colour in one line (see Colours and themes).

There is no build step. The kit is plain HTML, one CSS file and three small JavaScript files. The only library is Chart.js, which is included in the download.

Files

NexaAdmin/
  index.html            dashboard
  analytics.html        visitors, pages, devices, countries
  ecommerce.html        products, sales, categories
  orders.html           data table: search, filter, sort, pages, bulk actions, CSV
  users.html            team cards, users table, invite form
  calendar.html         month calendar with events
  messages.html         inbox and conversation
  invoice.html          printable invoice
  profile.html          user profile
  settings.html         profile, security, notifications, appearance, billing
  components.html       every interface part
  blank.html            empty starter page
  login.html  register.html  forgot-password.html  404.html
  documentation.html    this guide
  assets/
    css/nexa.css        all styles
    js/nexa.js          theme, sidebar, menus, modals, toasts, tabs, forms, tables
    js/charts.js        the charts
    js/pages.js         sample logic for orders, users, products, calendar, messages
    vendor/chart.umd.min.js   Chart.js 4 (MIT license, see chart.LICENSE.md)

Making a page

Copy blank.html and rename it. Put your content inside <main class="content">. Each direct child of content is one row with even spacing.

<main class="content" id="main">
  <section class="card">
    <div class="card-head"><div><h2>Title</h2><p>Short description</p></div></div>
    <div class="card-body">Your content</div>
  </section>
</main>

Add it to the sidebar

The sidebar is repeated in every page file. Add your link in each one. The page that is open carries aria-current="page", which also highlights it.

<a class="side-link" href="reports.html" aria-current="page" title="Reports">
  <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20V10M10 20V4M16 20v-7M22 20H2"/></svg>
  <span>Reports</span>
</a>

If your project has a template language (PHP includes, Blade, Twig, Django, EJS, React), move the sidebar and top bar into one shared partial so you only edit them once.

Page title and breadcrumb

Change the <h1> and the crumbs list in the top bar, and the <title> in the head.

Icons

Icons are inline SVG with the class icon. They take the text colour of their parent. Copy one from any page, or paste a 24 by 24 stroke icon from a free set such as Lucide or Feather.

Colours and themes

Brand colour

Open assets/css/nexa.css and change one line at the top. Buttons, active links, charts, focus rings and soft tints all follow.

:root {
  --brand: #5b54e8;       /* your colour */
  --brand-ink: #ffffff;   /* text on top of it: use #111 for a light brand colour */
}

Light and dark

The theme is the data-theme attribute on <html>: light or dark. The sun and moon button in the top bar switches it. The choice is saved in the browser and applied on every page before it paints, so there is no flash.

What you wantHow
Dark by defaultAdd data-theme-default="dark" to the <html> tag of each page
Follow the visitor's devicedata-theme-default="auto"
A button that sets a theme<button data-theme-set="dark">, with light, dark or auto
A button that flips it<button data-theme-toggle>
From your own scriptNexa.setTheme('dark')

Accent picker

The swatches on the settings page use data-accent="#0e8f7e". A click saves that colour in the browser and applies it everywhere. Remove the swatches if you want one fixed brand colour.

All tokens

Every colour is a CSS variable in the first section of nexa.css, with a second set under [data-theme="dark"]. The components page shows them as swatches.

VariableUsed for
--bg, --surface, --surface-2Page background, cards, and quiet areas such as table headers
--line, --line-strongBorders
--ink, --ink-2, --ink-3Main text, secondary text, small notes
--ok, --warn, --bad, --infoStatus colours, each with a -soft background partner
--side-bgSidebar background
--sidebar-w, --topbar-h, --r-sm, --r-md, --r-lgSizes and corner radius
--fontFont family. The kit loads Onest from Google Fonts

Layout and grid

Wrap cards in a grid with one of these column classes. They collapse on smaller screens by themselves.

ClassDesktopTabletPhone
grid cols-44 columns21
grid cols-3321
grid cols-2221
grid cols-2-1wide and narrow11
grid cols-1-2narrow and wide11

Helpers: row lines items up with a gap, row-end pushes an item to the right, stack spaces items vertically, muted is quiet text, num gives numbers equal widths, hide-xs hides on small phones.

Sidebar

Below 992 pixels the sidebar becomes a drawer opened by the menu button. On larger screens the button at the top left collapses it to icons, and the choice is saved. To start collapsed, add class="side-collapsed" to <html>.

Interface parts

Open components.html next to this guide. Each block there names its classes.

PartClasses
Cardcard with card-head, card-body, card-foot
Stat cardcard stat with stat-label, stat-icon tone-brand, stat-value, stat-foot
Buttonbtn, plus btn-primary, btn-danger, btn-ghost, btn-sm, btn-lg, btn-block. Square: icon-btn
Badgebadge, plus ok, warn, bad, info, brand. Add plain to hide the dot
Alertalert, plus ok, warn, bad
Avataravatar, plus sm, lg, xl. Colour with style="--c:#12a594". Put an <img> inside for a photo
Progressprogress with style="--v:72%" and an empty <i> inside
Tabstabs with role="tablist", buttons with role="tab" and aria-controls
Segmented buttonsseg with data-seg="name". Fires a nexa:seg event with the chosen value
Listslist, timeline, legend
Statesempty, skeleton

Tone classes colour an icon tile or a calendar event: tone-brand, tone-ok, tone-warn, tone-bad, tone-info.

Modals, menus, toasts

These are switched on with attributes. You do not need to write JavaScript for them.

Modal

<button class="btn" data-modal-open="myModal">Open</button>

<div class="modal" id="myModal" role="dialog" aria-modal="true" aria-labelledby="myTitle" aria-hidden="true">
  <div class="modal-box">
    <div class="modal-head"><h2 id="myTitle">Title</h2></div>
    <div class="modal-body">...</div>
    <div class="modal-foot"><button class="btn" data-modal-close>Close</button></div>
  </div>
</div>

Focus moves into the modal and stays there. Escape, a click on the dark area or any data-modal-close button closes it and returns focus to the button that opened it. From a script: Nexa.openModal('myModal') and Nexa.closeModal().

Dropdown menu

<div class="dropdown">
  <button class="btn" data-dropdown aria-expanded="false" aria-haspopup="true">Menu</button>
  <div class="menu">
    <a href="#">Edit</a>
    <hr>
    <a class="danger" href="#">Delete</a>
  </div>
</div>

Toast

<button class="btn" data-toast="Saved">Save</button>
<button class="btn" data-toast="Could not save" data-toast-type="bad">Fail</button>

Nexa.toast('Saved');           // from a script
Nexa.toast('Could not save', 'bad');

Forms

Put each control in a field with a label and an empty error element.

<form data-validate>
  <div class="field">
    <label for="email">Email</label>
    <input class="input" id="email" name="email" type="email" required>
    <span class="error"></span>
  </div>
  <button class="btn btn-primary" type="submit">Save</button>
</form>

data-validate checks the form before it is sent and shows a message under each field that fails.

RuleHow to set it
Must be filled inrequired. Custom message: data-required="Please accept the terms."
Valid emailtype="email"
Minimum lengthminlength="8"
Must equal another fielddata-match="idOfTheOtherField"

Demo mode

The forms in the kit also carry data-demo. In demo mode a valid form is not sent anywhere. It shows the data-success message as a toast, closes its modal, and goes to data-redirect if one is set.

To send a form to your server, remove data-demo and add action and method. The checks still run first.

<form data-validate action="/login" method="post">

The sign in, register and reset pages are interface only. Real sign-in needs a server. Never keep passwords in the page.

A peek button inside input-wrap shows and hides a password. A switch is a checkbox styled as a toggle.

Data tables

Add data-table and an id to a normal table. The rows stay in the HTML, so your server can print them as usual.

<input type="search" data-table-search="#orders">
<select data-table-filter="#orders" data-key="status">
  <option value="">All</option><option>Paid</option>
</select>

<table class="table" id="orders" data-table data-page-size="8">
  <thead><tr>
    <th data-sort="text">Customer</th>
    <th data-sort="date">Date</th>
    <th data-sort="num">Amount</th>
  </tr></thead>
  <tbody>
    <tr data-status="Paid">
      <td>Sara Lin</td>
      <td data-value="2026-05-27">May 27, 2026</td>
      <td data-value="299">$299.00</td>
    </tr>
  </tbody>
</table>

<span data-table-info="#orders"></span>
<nav class="pager" data-table-pager="#orders"></nav>
AttributeWhat it does
data-sort on a thMakes the column sortable as text, num or date
data-value on a tdThe value to sort by when the visible text is formatted
data-table-filter with data-key="status"Keeps rows whose data-status equals the chosen option
data-table-bulk="#orders"A bar shown while rows are ticked. Put <span data-count> inside for the number
data-table-empty="#orders"An element shown when no row matches
data-table-export="#orders"A button that downloads the matching rows as CSV. Columns marked data-no-export are left out

From a script

var t = Nexa.tables.orders;     // by table id
t.add(rowElement, true);        // true puts it at the top
t.remove(t.selected());         // delete the ticked rows
t.render();                     // after you change rows yourself
t.download('orders.csv');

The table works on the rows that are in the page. For very large data sets, page and search on your server and print one page of rows at a time.

Charts

Charts are drawn with Chart.js 4, included in assets/vendor. All of them are set up in assets/js/charts.js. A page with charts loads three scripts in this order:

<script src="assets/js/nexa.js"></script>
<script src="assets/vendor/chart.umd.min.js"></script>
<script src="assets/js/charts.js"></script>

Put the canvas in a chart-box with a height:

<div class="chart-box" style="height:300px">
  <canvas id="signupsChart" role="img" aria-label="Line chart of sign-ups per month"></canvas>
</div>

Describe the chart once with Nexa.chart. It receives the current theme colours and is redrawn when the theme or accent changes.

Nexa.chart('signupsChart', function (c) {
  return {
    type: 'line',
    data: {
      labels: ['Jan', 'Feb', 'Mar', 'Apr'],
      datasets: [{ label: 'Sign-ups', data: [120, 180, 150, 240], borderColor: c.brand, tension: 0.4 }]
    },
    options: { scales: { y: { grid: { color: c.line } } } }
  };
});

c holds brand, ink, ink2, ink3, line, surface and series, a list of six colours for several data sets.

The numbers in charts.js are samples. Replace the arrays with your own data.

Page scripts

assets/js/pages.js holds the sample behaviour for five pages. Each block starts with a comment and only runs on its own page. Data is kept in memory, so it resets when the page reloads.

PageWhat the script doesWhere to connect your data
Orders, usersAdds a row from the modal form, deletes one row or the ticked rows, marks orders completedThe nexa:submit handlers and the #confirmDelete click
E-commerceFilters the product grid by the data-state of each cardPrint your products as cards
CalendarDraws the month, lists the day, adds and deletes eventsThe events array
MessagesShows conversations, opens a thread, sends a message, searches peopleThe threads array and the form submit handler

Everything typed by a user is escaped with Nexa.escapeHtml before it is put in the page. Keep doing that when you add your own fields.

Connecting your data

NexaAdmin is the interface. It does not include a server, a database or sign-in. Two common ways to use it:

  • Server-rendered apps (PHP, Laravel, Rails, Django, Express): turn each page into a template, print your rows and numbers into the HTML, and keep the scripts as they are.
  • API-driven pages: fetch JSON, build the rows or chart data, then call table.render() or Nexa.chart(...).
fetch('/api/orders')
  .then(function (r) { return r.json(); })
  .then(function (orders) {
    var body = document.querySelector('#ordersTable tbody');
    body.innerHTML = orders.map(function (o) {
      return '<tr data-status="' + Nexa.escapeHtml(o.status) + '"><td>' + Nexa.escapeHtml(o.customer) + '</td>...</tr>';
    }).join('');
    Nexa.tables.ordersTable.render();
  });

Browser support

Current versions of Chrome, Edge, Firefox and Safari, on desktop and mobile. The soft colour tints use CSS color-mix(), available in Chrome and Edge 111, Firefox 113, Safari 16.2 and newer.

Every page was checked at 320, 375, 768, 1024 and 1366 pixels wide in both themes. Motion is reduced for visitors who ask their device for it, and the invoice has a print layout.

Help and license

For help, reply to your purchase receipt email and say which page you are working on.

You may use the kit in unlimited projects for yourself and for clients. You may not resell or redistribute the kit itself. The full terms are in LICENSE.txt. Chart.js is included under its own MIT license.