AtelierDocs
Live demo

Introduction

Thank you for purchasing Atelier. You have picked up a complete admin & dashboard template built as one standalone React 19 project — TypeScript, Vite, React Router and Tailwind CSS v4, over a design-token core. This document covers what is in the box, how to run it, how to theme it, and how to extend it.

What is Atelier?

Atelier is a starter kit for back-office software: SaaS consoles, analytics panels, e-commerce managers, CRMs, internal tools. It ships 114 pages — 17 niche dashboards, 10 full-screen web apps, a complete eCommerce suite, CRM / projects / crypto / NFT / jobs / blog modules, authentication and error screens, a UI component catalog, forms, tables, charts, maps and icons.

It is a front-end template. There is no database, no user model, no migrations and no auth backend. What you get is the entire interface layer, built to a single standard, ready for your own domain logic to be dropped in beside it.

The Bold Press design language

Atelier's look is deliberate and opinionated. Where most admin templates reach for soft shadows, big radii and glassy translucency, Atelier is built on four rules:

  • One ink. A single --at-ink color carries every border, every shadow and all strong text. Depth and emphasis are one gesture, not three.
  • Hard shadows. Offset, zero blur. A shadow is an object with an edge, not atmosphere.
  • The press. Every clickable surface travels exactly its shadow offset on :active and lands flush against the page. Buttons feel mechanical.
  • Sharp geometry. Radii live between 0 and 6 pixels. Nothing is pillowy.

Every one of those is a token, not a hard-coded value — which is what makes the twelve color schemes and twelve type sets described later swap cleanly instead of half-applying.

React, not a port with React sprinkled on

Everything interactive is a real React component or hook: the collapsing sidebar, the dropdowns, the modals, the offcanvas customizer, the ⌘K command palette, the chart wrapper and the map. There is no jQuery, no Bootstrap, no CSS-in-JS and no third-party component library whose look you would have to fight.

ConcernHow it is expressed
Pages114 lazy-loaded route components under src/pages/, auto-registered by filename
ShellLayout / AppShell / BareShell, chosen by route group
StateHooks and one CustomizerContext — no Redux, no Zustand, nothing to learn
StylingTailwind v4 utilities over --at-* tokens, compiled once at build time
TypesTypeScript in strict mode; the build type-checks before it bundles

Feature highlights

AreaWhat you get
Pages114 routes
Dashboards17 — Sales, Analytics, eCommerce, CRM, Projects, Finance & Banking, Crypto, NFT, Jobs, HR & Payroll, LMS, Stocks, Healthcare, POS, Social, School, Podcast
Apps10 full-screen apps — Email, Chat, Calendar, Kanban, To-Do, File Manager, Gallery, Contacts, Notes, Media Player
Theming12 color schemes × 12 type sets × light / dark / system — composable, not fixed pairs
Layout3 sidebar styles, fluid or boxed width, collapsible icon rail, mobile drawer
DirectionFull RTL via logical CSS properties — the layout flips, the tokens do not
Data vizApexCharts wrapper that reads its palette from tokens and re-themes live
Extras⌘K command palette, live customizer, breadcrumb + active-trail engine driven by one manifest
StackReact 19, TypeScript 5.9, React Router 7, Vite 6, Tailwind CSS v4 — no jQuery, no Bootstrap, no icon font

What's Included

Unzip the download and you will find two folders:

atelier-react/
├── Documentation/
│   └── index.html            ← this file
└── template/
    ├── src/                  the React application
    ├── public/               static files served as-is
    ├── index.html            the Vite entry + anti-flash script
    ├── package.json          dependencies and the four npm scripts
    ├── vite.config.ts
    ├── tsconfig.json  tsconfig.app.json  tsconfig.node.json
    └── README.md             the 60-second version of this file

template/

A complete, independent project. Copy it out, rename it, and work in it — nothing outside the folder is needed and nothing inside it reaches out.

The dependency tree is not shipped. There is no node_modules/ in the package — run npm install and it is fetched from npm at the versions pinned in package.json.

Documentation/

One self-contained HTML file — the one you are reading. It has no build step and no dependencies beyond three webfonts, so it opens correctly straight off disk with a double-click.

License and changelog

Atelier is licensed through Envato under the Regular or Extended license you selected at checkout; the definitive terms are on your ThemeForest download page. Third-party libraries keep their own licenses — see Credits. Release history is in the Changelog.

The photographs, avatars and illustrations used in the demo content are for preview purposes only. They are not licensed for redistribution in your product. Swap them for your own media before you ship.

Requirements

Short list — this is a static front end, so almost everything here is for your machine, not your server.

WhatVersionNeeded for
Node.js20.19+ or 22.12+The dev server and the production build
npm10+ (ships with Node)Installing dependencies. Yarn and pnpm work too.
Your serverNothing. The build output is static files.

The Node floor is Vite's, not ours. Vite 6 refuses to start on anything below 20.19 or 22.12. Check yours with node -v.

Node is a build tool, not a runtime

Node compiles the TypeScript, the CSS and the JavaScript once, into a folder of hashed static assets. Your production host never runs Node — it serves those files. You can build locally and upload the output to anything that serves HTML.

General tooling

  • A code editor — anything. VS Code with the Tailwind CSS IntelliSense extension is a comfortable default.
  • A terminal — every command in this document is run from the template/ folder.
  • A modern browser — the last two versions of Chrome, Edge, Firefox or Safari. Internet Explorer is not supported and never will be; the design leans on CSS custom properties, color-mix() and logical properties.

Quick Start

Three commands from download to a running app.

cd template
npm install
npm run dev          # http://localhost:5181

The entry route resolves to /dashboards/sales. If the first thing you see is the Sales dashboard, everything is wired correctly.

The four scripts

CommandWhat it does
npm run devVite dev server on port 5181, with hot module replacement.
npm run buildtsc -b then vite builddist/. A type error fails the build.
npm run previewServes dist/ locally, so you can check the real build before deploying.

If the page loads unstyled

The dev server compiles Tailwind on the fly, so an unstyled page almost always means npm install did not finish. Delete node_modules/ and the lockfile, run it again, and watch for an error rather than a warning.

The React App

Overview

React 19 with TypeScript, routed by React Router as a single-page app, styled by a Tailwind v4 token core. Dropdowns, modals, the customizer, the offcanvas sidebar and the command palette are all native React components and hooks — there is no second interactivity layer to reason about.

Installation and development

cd template
npm install
npm run dev        # http://localhost:5181

Production build

npm run build      # tsc -b && vite build → dist/
npm run preview

The build type-checks first, so a type error fails the build rather than shipping. Output is a static SPA bundle.

Because this is a single-page app, your host must rewrite unknown paths to index.html or a deep link like /apps/chat will 404 on refresh. See Deployment for the one-line config on each major host.

Project structure

template/
├── src/
│   ├── main.tsx  App.tsx
│   ├── pages/                    114 screens, mirroring the route slugs
│   │   └── pageRegistry.ts       auto-glob: filename → slug → lazy import
│   ├── components/
│   │   ├── shell/                sidebar, header, footer, customizer, palette
│   │   ├── ui/                   the component catalog
│   │   ├── charts/  maps/  auth/
│   ├── context/CustomizerContext.tsx
│   ├── hooks/                    useClickOutside, useFocusTrap
│   ├── lib/                      storage · theme · nav · sidebar · manifest
│   ├── data/                     nav manifest + demo JSON
│   └── styles/                   the design-token core and app layers
├── index.html
└── vite.config.ts

Notes

  • src/lib/storage.ts, theme.ts and nav.ts own the at: localStorage keys and the data-at-* attributes on <html>. Everything that reads or writes theme state goes through them; nothing else touches localStorage directly.
  • The anti-flash script still runs first in index.html, before React mounts. Removing it reintroduces a flash of the wrong theme on every load.
  • Routes are not hand-written. src/pages/pageRegistry.ts globs every module under src/pages/, derives each one's manifest slug from its filename (dashboards/Sales.tsxdashboards/sales) and lazy-imports it — so adding a screen is adding a file. A slug that a filename cannot express (TSX files may not start with a digit, so error/404 is Error404.tsx) is pinned in the SLUG_OVERRIDES map at the top of that file.

Project Structure

Four pillars carry the whole template. Recognize them once and the rest of the tree explains itself.

The four pillars

PillarWhat it isWhere
styles/The Tailwind v4 entry, the token chain and the app layerssrc/styles/, imported once in src/main.tsx
data/nav-manifest.json (the route registry) + demo JSONsrc/data/, bundled via resolveJsonModule
lib/Theme restore, storage, customizer registry, nav, manifest helperssrc/lib/ — plain TypeScript, no React imports
The <head> contractAnti-flash script, fonts, meta — in that orderindex.html, before React mounts

The stylesheet, in load order

src/styles/app.css is the single entry point for CSS. Its import order is the design system's law — primitives before roles, roles before schemes, schemes before recipes:

/* 1 */ @import 'tailwindcss';

/* 2 — the token chain */
@import './tokens/_primitives.css';   raw scales, the ink and shadow system
@import './tokens/_roles.css';        role aliases: light :root + [data-at-theme=dark]
@import './tokens/_themes.css';       11 color schemes (Atelier is inline on :root)
@import './tokens/_typography.css';   11 type sets (Atelier is inline on :root)
@import './tokens/_charts.css';       the data-viz palette
@import './tokens/_recipes.css';      .at-card, .at-press, .at-stamp, focus rings

/* 3 — app layers */
@import './base.css';  './components.css';  './nav.css';
@import './shell.css'; './utilities.css';   './schemes.css';

/* 4 — @theme bridge: --at-* roles → Tailwind utilities */

Nothing below the token chain invents a value. Every color, radius, spacing step and shadow in base, components, nav, shell and schemes resolves through a --at-* variable — which is precisely why a scheme swap re-paints the entire application without a rebuild.

The TypeScript core

src/lib/ holds the framework-free half of the app: plain modules with no React imports, so they are testable on their own and safe to call from anywhere.

ModuleResponsibility
lib/storage.tsThe at: localStorage helper. Never throws — storage may be blocked.
lib/theme.tsApplies theme, scheme and type set; tracks OS theme when the mode is system.
lib/nav.tsActive trail, breadcrumb and page title, derived from the manifest.
lib/sidebar.tsThe collapsed icon rail and the mobile drawer.
lib/manifest.tsLoads the route registry and resolves slugs to nodes.
ComponentResponsibility
context/CustomizerContext.tsxThe authoritative attribute ↔ storage-key ↔ default map, exposed as one provider.
components/shell/CommandPalette.tsx⌘K / Ctrl-K search across every route.
components/ui/Icon.tsxThe inline-SVG renderer.
components/charts/ApexChart.tsxThe ApexCharts wrapper — lazy import, token palette, live re-theme.

One manifest, three surfaces

data/nav-manifest.json is read once per page and drives three things at the same time: the sidebar's active trail and open groups, the breadcrumb above each page title, and the ⌘K command palette's index. Add a route there and all three pick it up. That is covered in Adding pages.

Theming & Design Tokens

Everything visual in Atelier is a CSS custom property in the --at-* namespace. There are no hex codes in the component layer. This is what makes twelve color schemes, twelve type sets and a dark mode possible without a rebuild — and it is what you should work with when you brand the template.

How the tokens are layered

Three layers, each one only allowed to consume the layer above it:

LayerFileHolds
1 — Primitives_primitives.cssRaw scales: the spacing ramp, the radius ramp, the type ramp, the ink and hard-shadow system, semantic color ramps. Values with no meaning attached.
2 — Roles_roles.cssMeaningful aliases: --at-canvas, --at-surface, --at-ink, --at-text-muted, --at-accent. Declared for light on :root and re-declared for dark under [data-at-theme='dark'].
3 — Schemes_themes.cssEleven blocks that override layer 2 for the eleven non-default schemes, each with its own light and dark pair. Atelier, the default, is the inline :root set in layer 2.

A fourth file, _recipes.css, turns those roles into the Bold Press primitives every component composes: .at-card (surface + 2px ink border + hard shadow), .at-press (the mechanical depress), .at-stamp (the solid active-nav marker) and the focus ring.

The roles you will actually touch

TokenRole
--at-inkThe signature. Every border, every hard shadow, all strong text.
--at-canvasThe page behind everything.
--at-surface, --at-surface-subtleCards, panels, table headers, wells.
--at-text, --at-text-muted, --at-text-subtleThe three body-copy weights.
--at-accentThe primary fill — buttons, the active nav stamp, chart series 1.
--at-accent-textThe primary foreground — links and accent-colored type. A darker cut of the same hue, because a color bright enough to sit behind ink is never dark enough to read as text on the canvas.
--at-on-accentWhat sits on top of an accent fill.
--at-secondary, --at-tertiary, --at-limeThe supporting color blocks.
--at-success, --at-warning, --at-danger, --at-infoStatus.

--at-accent and --at-accent-text are two separate tokens on purpose. If you brand the template and set only one of them, either your buttons or your links will fail contrast. Set both.

Changing the look

Pick the smallest hammer that does the job.

1. Re-brand the default scheme

Open styles/tokens/_roles.css and change the role values on :root (light) and on [data-at-theme='dark'] (dark). This re-brands the whole application — every page, every component, every chart — and the other eleven schemes are untouched.

:root {
  --at-ink:          #1b2430;   borders, shadows, strong text
  --at-canvas:       #eef1f5;
  --at-surface:      #ffffff;
  --at-accent:       #6ea8fe;   the fill
  --at-accent-text:  #1f5bbd;   the foreground — do not skip this
  --at-on-accent:    #1b2430;
}

2. Add a thirteenth scheme

Append a block to _themes.css following the shape of the existing eleven, then add a matching tile to the customizer panel:

[data-at-theme-preset='harbor'] {
  --at-ink: …;  --at-canvas: …;  --at-accent: …;  …the full role set
}
[data-at-theme='dark'][data-at-theme-preset='harbor'] {
  …the dark counterpart. Both are required.
}

3. Use the Tailwind utilities

The @theme block at the bottom of app.css bridges every role token into Tailwind's namespaces, so bg-surface, text-ink, border-default, text-accent, shadow-card and font-display all exist as ordinary utilities. Because the bridge is late-bound through var(), a runtime scheme swap re-paints those utilities too — no rebuild.

Prefer a utility or a token over a literal value. bg-surface follows the reader into dark mode and into all twelve schemes; bg-[#fff] does not.

Color Schemes & Dark Mode

Light, dark and system

Mode is stored under at:theme as one of light, dark or system. The resolved value — never the literal system — is written to <html data-at-theme>, so CSS only ever has two states to match. When the mode is system, a matchMedia listener re-resolves it if the reader changes their OS setting while the tab is open.

The twelve color schemes

Each scheme is a complete identity — canvas, ink, accent, secondary, tertiary and the full semantic set — with a purpose-built dark counterpart, not a mechanical inversion. Switching one changes the world the app lives in, not a single highlight color.

Atelier
Electric
Sage
Violet
Lagoon
Foundry
Stencil
Gantry
Lacquer
Nocturne
Primer
Sorbet

The scheme id is stored under at:theme-preset and written to <html data-at-theme-preset>. Atelier is the default and writes no attribute — its values are the inline :root set, so the DOM stays clean on an untouched install. Every other scheme writes its id.

<!-- Sorbet, dark -->
<html data-at-theme="dark" data-at-theme-preset="sorbet">

<!-- Atelier, light — note the absent attributes -->
<html data-at-theme="light">

Dark mode is designed, not derived

Every scheme has a hand-tuned dark pair. In dark, Atelier splits the ink token in two, and the other schemes follow the same rule:

  • --at-ink becomes a muted cut of the accent hue — enough contrast to read as an edge, not so much that a page of bordered cards glows.
  • --at-ink-strong keeps full strength and is what type and solid fills use.

Collapsing them back into one value is the single most common way to break dark mode here: borders shout or text mumbles, and there is no value that satisfies both.

The anti-flash script

The first executable thing in every <head>, before any stylesheet, is a small synchronous IIFE. It reads the at: keys, resolves system through matchMedia, and writes every data-at-* attribute plus lang and dir onto <html> before the first paint. It runs in about a millisecond and is wrapped in try/catch so blocked storage can never stop the page.

Do not move it below the stylesheet, do not defer it, and do not convert it to a module. Any of those reintroduces a flash of the wrong theme on every single page load. It is duplicated in every edition for exactly this reason.

Storage schema and migrations

All keys are namespaced at: and versioned by at:schema, currently 2. On boot the script compares the stored version: a recognized older version is migrated, an unrecognized shape is wiped rather than trusted. That is what stops a stale key from a previous major version rendering someone a broken layout.

Typography

Typography is its own axis, independent of the color scheme. Twelve type sets live in tokens/_typography.css under data-at-type-preset, and any of them composes with any of the twelve color schemes — 144 combinations, all valid. Sorbet's palette with Stencil's condensed display face is a supported choice.

Type setDisplayBody
Atelier (default)FrauncesLibre Franklin
ElectricArchivoInstrument Sans
SageBricolage GrotesqueManrope
VioletSoraManrope
LagoonOutfitFigtree
FoundrySairaIBM Plex Sans
StencilAntonioPublic Sans
GantryOxaniumIBM Plex Sans
LacquerPlayfair DisplayDM Sans
NocturneChivoMulish
PrimerZilla SlabPublic Sans
SorbetGabaritoPlus Jakarta Sans

JetBrains Mono is constant across all twelve. It sets every figure in the interface — KPI values, table numerics, chart axes, code — so numbers stay column-aligned whatever else changes.

The three font tokens

TokenUsed for
--at-font-displayHeadings, KPI values, the wordmark, nav labels. Consumed at weights 600–800 and at sizes down to 12px — so a replacement family must ship those as real drawn weights, not synthesised ones.
--at-font-sansBody copy, form labels, table cells, everything else.
--at-font-monoAll numerics and code.

Swapping in your own fonts

Two edits, in this order:

  1. In index.html, replace the Google Fonts <link> with your own — self-hosted @font-face rules work equally well.
  2. In tokens/_roles.css, point --at-font-display / --at-font-sans / --at-font-mono at the new families, keeping a system fallback stack.

All twelve type sets are requested in a single Google Fonts URL, so switching a type set at runtime re-types the page instantly with no new network request. If you only ever ship one set, trimming that URL down to it is the single largest easy win on page weight.

RTL & Languages

How RTL works here

Atelier is built with logical CSS properties throughout — margin-inline-start rather than margin-left, padding-block rather than padding-top/bottom, inset-inline-end rather than right. Setting dir="rtl" on <html> therefore flips the entire layout — sidebar, nav carets, table alignment, charts, the customizer panel — while changing no token values at all. There is no separate RTL stylesheet to maintain and no app.rtl.css to keep in sync.

Turning on RTL

Three ways, all equivalent:

  • The customizer — Direction → RTL. Applies live, persists to at:dir.
  • The header language menu — picking العربية sets lang="ar" and flips direction with it.
  • In codedocument.documentElement.setAttribute('dir','rtl').

Making RTL the default

Edit the anti-flash script's direction default in index.html. It currently derives direction from the stored language, defaulting to LTR:

/* before */
var dir = dirStored ? dirStored : (lang === 'AR' ? 'rtl' : 'ltr');

/* after — RTL unless the reader has chosen otherwise */
var dir = dirStored ? dirStored : 'rtl';

Change <html lang="en"> to your locale at the same time, and if that locale needs a different script, swap the font families as described in Typography.

The language menu

The header ships a language dropdown with English, العربية and Español. It is a direction and locale switch, not a translation engine — the template ships no translation strings, because which i18n library you use is your decision, not ours. Picking a language sets lang, derives dir (Arabic → RTL), and persists both:

setLang(code) {
  document.documentElement.setAttribute('lang', code)
  const dir = code === 'ar' ? 'rtl' : 'ltr'
  document.documentElement.setAttribute('dir', dir)
  storage.set('lang', code.toUpperCase())
  storage.set('dir', dir)
}

To wire real translations, hook your i18n library into that same function — the direction handling is already done for you. To add a language, add a menu item; to add another RTL locale (Hebrew, Persian, Urdu), extend the code === 'ar' test to a set.

If you write new markup, use logical properties. A single margin-left is invisible in LTR and misaligns the moment someone flips the app.

Live Customizer

The customizer is the panel behind the palette button in the header. Every control applies live — no reload — and persists, so a reader's configuration survives navigation and return visits.

Controls

ControlOptionsStorage keyAttribute
Color Schemethe 12 schemesat:theme-presetdata-at-theme-preset
Typographythe 12 type setsat:type-presetdata-at-type-preset
ModeLight · Dark · Systemat:themedata-at-theme (resolved)
DirectionLTR · RTLat:dirdir
Sidebar StyleDefault · Compact · Expandat:sidebar-styledata-at-sidebar-style
WidthFluid · Boxedat:widthdata-at-width
Resetclears every keyclears every attribute

The three sidebar styles are genuinely different geometries, not degrees of one:

  • Default — collapsible. The header's hamburger toggles between the full sidebar and a narrow icon rail. Collapsed is the initial state.
  • Compact — locked to the icon rail, with flyout submenus for nested routes.
  • Expand — locked open at full width.

Compact and Expand each lock one geometry, so there is nothing to collapse and the desktop hamburger is hidden for both. Below 993px all three become the same thing — the sidebar is an off-canvas drawer and the hamburger is its trigger.

The scheme and type-set tiles

Each color tile paints its own scheme's canvas, ink, accent and secondary as literal hex values, and each typography row renders its sample in the faces it applies. Both have to show a world that is not the active one, which is the one place in the codebase where hardcoded values are correct. If you add or re-brand a scheme, update its tile in partials/customizer.html to match — nothing else reads those values.

Beyond the panel

The REGISTRY in js/core/customizer.js is the authoritative map of every tunable axis, and it carries more than the panel exposes:

Registry nameAttributeKeyDefault
menudata-at-menuat:menuclick
pagedata-at-pageat:pageregular
headerPositiondata-at-header-positionat:header-positionfixed
sidebarPositiondata-at-sidebar-positionat:sidebar-positionfixed
headerSchemedata-at-headerat:header-schemelight

Set any of them from the console or from your own code, and they behave exactly like the panel's controls:

import { setByName } from './js/core/customizer.js'
setByName('headerPosition', 'static')

To surface one in the panel, add a segmented control that calls setByName() with that name. No other wiring is needed — the registry already knows the attribute, the key and the default.

Navigation orientation is hard-coded vertical, and the sidebar surface scheme follows light/dark automatically. Neither is a tunable, which is why neither appears above.

Making a Configuration the Default

The customizer is a demo affordance. In a real product you usually want your configuration to be what every visitor sees — and often you want the panel gone entirely.

How settings persist

Every axis follows one convention, and understanding it is most of the work:

The default writes nothing. Selecting a value equal to the default removes the attribute from <html> and deletes the storage key. Only non-defaults are written. That keeps the DOM clean and makes "has this reader chosen anything?" a question storage can answer.

One key is deliberately inverted. at:collapsed exists because the icon rail is the default state for the Default sidebar style — so the attribute is written when the key is absent, and the key stores '0' only as the opt-out the hamburger writes when a reader expands the sidebar.

The full key list

KeyValuesDefault
at:schema2written on boot
at:themelight · dark · systemlight
at:theme-preseta scheme idatelier
at:type-preseta type-set idatelier
at:lang · at:dirEN/AR/ES · ltr/rtlEN · ltr
at:sidebar-styledefault · compact · expanddefault
at:collapsed'0' = expanded (inverted)absent = collapsed
at:widthfluid · boxedfluid
at:menu · at:pageclick/hover · regular/…click · regular
at:header-position · at:sidebar-positionfixed · staticfixed
at:header-schemelight · darklight
at:accent-customa hex stringabsent

Baking your configuration in

Step 1 — find the configuration you want. Open the app, set the customizer exactly how you like it, then read the result back from the console:

Object.keys(localStorage)
  .filter(k => k.startsWith('at:'))
  .forEach(k => console.log(k, '=', localStorage.getItem(k)))

Step 2 — change the defaults in the anti-flash script. In index.html, the fallbacks are the defaults. Change them and every first-time visitor gets your configuration before the first paint:

/* was */  var theme  = get('at:theme') || 'light';
/* now */  var theme  = get('at:theme') || 'dark';

/* was */  var preset = get('at:theme-preset') || 'atelier';
/* now */  var preset = get('at:theme-preset') || 'nocturne';

Step 3 — match the registry. Update the same defaults in REGISTRY (js/core/customizer.js), or Reset will restore the old ones and the panel will show the wrong control as active. The two must agree.

Step 4 (optional) — remove the panel. Delete the customizer partial's include and the header's palette button. Nothing else depends on it; theming keeps working, readers just cannot change it.

Forcing a new default onto returning visitors

Readers who already have at: keys stored keep their old configuration — that is the point of persistence. To push a new default to everyone, bump the schema version: change the at:schema value the script writes (from '2' to '3') and let the mismatch branch wipe the at: namespace. Every visitor then re-derives from your new defaults on their next load.

That is a one-way reset — it discards every preference every reader has set. Use it when you have genuinely changed the product's identity, not for a routine deploy.

Adding & Editing Pages

The two things every page needs

  1. A file under src/pages/.
  2. A manifest entry, if you want it in the sidebar, the breadcrumb and the ⌘K palette.

The second is optional. A page with no manifest entry still renders and is still reachable by URL — it just is not advertised anywhere.

Where pages live

One file per route, at src/pages/<section>/<Page>.tsx. There is no route to register: src/pages/pageRegistry.ts globs every module under src/pages/, turns its filename into a slug (dashboards/Sales.tsxdashboards/sales) and lazy-imports it. Adding a screen is adding a file.

A slug a filename cannot express — TSX files may not start with a digit, so error/404 is Error404.tsx — is pinned in the SLUG_OVERRIDES map at the top of pageRegistry.ts.

Anatomy of a page

// src/pages/reports/Summary.tsx
import { PageHead } from '../../components/shell/PageHead';

export default function Summary() {
  return (
    <>
      <PageHead title="Summary" />

      {/* your content */}

    </>
  );
}

That is the whole contract. The shell — sidebar, header, footer, customizer, command palette — is supplied by the layout route, chosen from the slug: apps/* gets AppShell, auth/* and error/* get BareShell, everything else gets Layout. <PageHead> renders the breadcrumb and the <h1>, both resolved from the manifest, and each shell keeps data-at-route on <html> in step as you navigate.

Registering the route

Add a node to data/nav-manifest.json:

{
  "id":       "reports.summary",
  "title":    "Summary",
  "slug":     "reports/summary",
  "icon":     "chart-bar",
  "parent":   "grp.reports",
  "section":  "MAIN",
  "order":    1,
  "badge":    null,
  "keywords": ["summary", "report"],
  "inMenu":   true,
  "alias":    null,
  "external": false
}
FieldMeaning
idUnique. Dotted convention, <group>.<page>.
slugThe route, without extension. Must match data-at-route.
parentThe group node this nests under, or null for top level.
sectionWhich sidebar heading it falls under — MAIN, APPLICATIONS, MODULES, PAGES, UI & FORMS.
orderSort position within its parent.
keywordsExtra terms the ⌘K palette matches on.
inMenufalse keeps the route working but hides it from the sidebar.

Worked example — a new report page

  1. Copy src/pages/dashboards/Analytics.tsx to src/pages/reports/Summary.tsx.
  2. Rename the component to Summary and set <PageHead title="Summary" />.
  3. Strip the demo content, keeping the <PageHead>.
  4. Add the manifest node above (and a grp.reports group node if the group is new).
  5. npm run dev → the page is at /reports/summary, the sidebar shows it, the breadcrumb reads Reports › Summary, and ⌘K finds it.

Start from the page nearest to what you want rather than from a blank file. Every page in the template already carries the correct shell, spacing rhythm and component usage, and copying keeps you inside the design system by default.

Charts

All data visualization is ApexCharts, wrapped by src/components/charts/ApexChart.tsx. The wrapper does three things worth knowing about: it lazy-imports ApexCharts so a page with no chart never pays for it, it reads its palette from the --at-chart-* tokens, and it re-renders every chart on the page when the theme changes.

Using a chart

One component, fully typed. Drop it in and you are done:

import { ApexChart } from '../../components/charts/ApexChart';

<ApexChart
  type="area"
  height={280}
  categories={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']}
  series={[{ name: 'Revenue', data: [31, 40, 28, 51, 42, 109] }]}
/>
PropTypeDefault
type'line' | 'area' | 'bar' | 'donut' | 'pie' | 'radialBar' | 'polarArea'required
seriesApexOptions['series']required
categoriesstring[] — x-axis labels
labelsstring[] (donut / pie / radial)
heightnumber, px. On a chart that fills its card (an .at-chart__body that is a .at-card’s own child) this is a floor, not a fixed size: the chart is handed 100% and keeps re-fitting as the row’s height moves. Anywhere else — sparklines, a chart nested in a column — it is the exact height.260
colorstring — a hex, or a token name such as --at-accentthe token palette
sparklineboolean — strips axes, grid and paddingfalse
stacked · horizontalbooleanfalse
legend · values · trackbooleanfalse
tooltipbooleanfalse to suppresstrue
centerLabel · centerValuestring — donut center label and figure
unitstring — a suffix for formatted values

Charts for data that arrives later

Nothing special to do: series is a prop, so pass state and the chart follows it. The wrapper mounts ApexCharts once per element and updates in place rather than re-creating the instance.

const [series, setSeries] = useState<ApexAxisChartSeries>([]);

useEffect(() => {
  fetch('/api/revenue').then(r => r.json()).then(setSeries);
}, []);

return <ApexChart type="area" height={280} series={series} />;

Live re-theming

Charts are canvas and SVG, so unlike the rest of the interface they cannot inherit a CSS variable change. The wrapper listens for the at:change event that every customizer write dispatches, re-reads the --at-chart-* tokens and re-renders. That is why switching scheme or mode re-colors the charts along with everything else.

If you build a component that changes theme state without going through the customizer, dispatch the same event so charts stay in step:

window.dispatchEvent(new CustomEvent('at:change', { detail: { mode: 'dark' } }))

Axis formatting

The wrapper compacts axis numbers (7421074.2k) and computes a "nice" axis ceiling that hugs the data while landing on readable ticks — 0 · 4k · 8k · 12k, never 0 · 4.25k · 8.5k. Both behaviors are in ApexChart.tsx and can be replaced if your data wants different treatment.

ApexCharts touches window, so it is imported inside an effect rather than at module scope. If you move the template onto a server-rendered framework later, that is the line that keeps it from running on the server.

Maps & Icons

Maps

Mapping is Leaflet — open source, no API key, no account, no per-view billing. The demo is at maps/leaflet, and src/components/maps/LeafletMap.tsx imports the library inside an effect, so its ~150 kB only lands on the route that renders it.

Leaflet's own stylesheet and Atelier's overrides are static imports at the top of that component, because bundlers cannot code-split CSS that sits behind a dynamic import. Render <LeafletMap /> anywhere and both come with it.

Tiles come from OpenStreetMap by default. Swap in Mapbox, Stadia, Carto or your own tile server by changing the tile-layer URL; check the terms of whichever provider you point at before shipping.

Icons

Every icon in Atelier is an inline SVG. There is no icon font, no sprite sheet and no icon package in package.json. The advantages are practical: icons inherit currentColor, scale without hinting artifacts, cost no extra request and cannot flash-of-unstyled on load.

src/components/ui/Icon.tsx renders one, wrapping inner path data in the house contract — viewBox="0 0 24 24", fill="none", stroke="currentColor", round caps and joins — so every icon in the interface is geometrically consistent:

import { Icon } from '../../components/ui/Icon';

<Icon path='<circle cx="12" cy="12" r="10"/>' />
<Icon path={COMPASS} size={14} stroke={2.5} />

Color is never a parameter — it comes from the surrounding color, so an icon inside a button is automatically the button's foreground in every scheme and both modes.

Adding an icon

Pass the inner markup only; the wrapper <svg> is generated. Keep a shared set in a module and import it where you need it:

export const ICONS = {
  // …
  compass: '<circle cx="12" cy="12" r="10"/><path d="m16.24 7.76-2.12 6.36-6.36 2.12 2.12-6.36z"/>',
};

Any 24×24 stroke icon set drops in unchanged. For a one-off, paste the SVG straight into the JSX — that is what most pages do.

Match the stroke width of the icons already in place (2 for most, 2.5 for small chrome) or the new icon will read heavier or lighter than its neighbours at the same size.

Plugins Used

Atelier is built on a deliberately short dependency list. Nothing here is jQuery-era, nothing is a UI kit whose look must be fought, and every runtime library is present because a page genuinely needs it.

Runtime libraries

LibraryVersionUsed for
React · React DOM19The whole interface
React Router7Routing, from one nav manifest
Tailwind CSSv4The utility layer and the @theme bridge
ApexChartsv4Every chart — lazily imported
Leaflet1.9The interactive map — lazily imported
Floating UI1.8Dropdown, tooltip and popover positioning

Six runtime dependencies, and two of the six are fetched only by the routes that use them.

Build tooling

ToolRole
Vite 6Dev server and production bundler.
@vitejs/plugin-reactFast Refresh in development, JSX transform in the build.
@tailwindcss/viteTailwind v4's Vite integration. No PostCSS config, no tailwind.config.js — v4 is configured in CSS.
TypeScript 5.9strict mode. tsc -b runs as part of npm run build, so a type error fails the build.

What is deliberately absent

  • No jQuery, and no plugin that requires it.
  • No Bootstrap or other CSS framework beyond Tailwind.
  • No icon font — see Icons.
  • No CSS-in-JS, no styled-components, no runtime style injection — one compiled stylesheet.
  • No state library — hooks and one context are the whole state story.

Each library is distributed under its own license — see Credits.

Deployment

At a glance

npm run build     # type-check, then bundle → dist/
npm run preview   # check dist/ locally before you upload it

Upload dist/. Netlify, Vercel, Cloudflare Pages, GitHub Pages, S3 + CloudFront, or any nginx/Apache document root all serve it, and none of them need Node at runtime.

The one piece of configuration

This is a single-page app, so unknown paths must fall back to index.html — without it, a refresh on a deep link such as /apps/chat returns a 404:

# netlify.toml
[[redirects]]
  from = "/*"
  to   = "/index.html"
  status = 200
# vercel.json
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
# nginx
location / {
  try_files $uri $uri/ /index.html;
}
# Apache — .htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

Deploying under a subfolder

If the app will not live at the domain root, build with the path Vite should prefix its asset URLs with, and hand the same value to the router:

npm run build -- --base=/admin/

Then set basename={import.meta.env.BASE_URL} on the <BrowserRouter> in src/App.tsx, so route matching and asset loading agree on the prefix.

Caching

Every build content-hashes its asset filenames, so the compiled CSS and JS can be cached forever:

Cache-Control: public, max-age=31536000, immutable   # hashed assets
Cache-Control: public, max-age=0, must-revalidate     # HTML documents

nav-manifest.json and the demo JSON are bundled rather than fetched, so they are hashed with everything else. If you switch them to runtime fetch, keep them out of the long cache — a stable filename plus an immutable cache outlives every edit.

Frequently Asked Questions

Can I drop this into an existing React app?

Yes, piece by piece. src/styles/ is self-contained, so importing app.css gives you the token core, the Bold Press recipes and the Tailwind bridge on their own. From there, copy the shell components you want; they take props and context, not globals.

Can I use just the CSS?

Yes. src/styles/ is self-contained — copy it into your project and import app.css. You get the token core, the Bold Press recipes and the Tailwind bridge without any of the markup or any React at all. Interactive components need their JavaScript; static styling does not.

Is TypeScript required?

No. Rename the files to .jsx, drop tsc -b from the build script and it is a plain JavaScript project. You lose the type-check, not the template.

Can I move it onto a server-rendered React framework?

The pages and components are ordinary React with no Vite-only APIs, so they port. Two things need attention: routing is React Router here, and ApexCharts and Leaflet are browser-only — keep them behind a client boundary wherever the first render happens on the server.

How do I change the primary color?

Set --at-accent and --at-accent-text in tokens/_roles.css, for both the light :root block and the dark one. See Theming & tokens.

Does it support dark mode and RTL?

Both, first-class. Dark mode is hand-designed for every one of the twelve schemes, not derived by filter. RTL is built on logical properties, so dir="rtl" flips the layout with no separate stylesheet.

Can I remove the customizer?

Yes — drop <Customizer /> from the shells and the header's palette button. Theming keeps working; readers just cannot change it. See Default config.

Are the demo images licensed for production?

No. The photographs, avatars and illustrations are preview-only. Replace them with your own media, or media you hold rights to, before you ship.

Is there a backend, auth or database?

No. Atelier is the interface layer. The auth screens are UI: real forms, real validation states, no server behind them.

What browsers are supported?

The last two versions of Chrome, Edge, Firefox and Safari, desktop and mobile. Internet Explorer is not supported.

Where do updates come from?

Your ThemeForest Downloads page. Re-download the item to get the latest version; see Support & updates.

Support & Updates

Thank you for choosing Atelier. Here is where to look first, and how to reach a human if that is not enough.

Before you reach out

Most questions are answered fastest by what is already in your download:

  • This documentation — installation, building, deploying, theming and the structure of the project.
  • The live demo at atelier.jawad.work — every page and feature running, and a quick way to confirm expected behavior.
  • The code itself — the shared core is heavily commented, and the comments explain why a thing is the way it is, which is usually the question behind the question.

Getting help

Contact the author through the item's page on ThemeForest — the Comments tab for general questions, the author's Support tab for item support. Please include:

  • your Node and npm versions (node -v, npm -v),
  • what you expected versus what happened, and the exact error text if there is one.

Support email: jawad.ahbab.turna@gmail.com

Item support covers defects, questions about the included features, and help getting the template running. It does not cover customization work, third-party integration or your own application code — though if you want that built, ask; it can be arranged separately.

Updates

Atelier is updated through ThemeForest. When a new version ships, sign in and re-download the item from your Downloads page — your purchase entitles you to the updates published for this item. The Changelog records what changed in each release, so you can review before upgrading. Upgrading is a matter of merging the changed files into your project.

Keep your changes in version control from day one. Then pulling in a future release is a reviewable merge rather than a hunt for everything you once edited.

Changelog

v1.0.0 — Initial release

  • React 19 + TypeScript 5.9 + React Router 7 on Vite 6 and Tailwind CSS v4, over one --at-* token core.
  • 114 routes — 17 niche dashboards, 10 full-screen apps, a complete eCommerce suite, CRM / projects / crypto / NFT / jobs / blog modules, authentication and error screens, a UI component catalog, forms, tables, charts, maps and icons.
  • The Bold Press design language: one ink token driving borders, hard offset shadows and strong text, plus the mechanical press interaction on every clickable surface.
  • Twelve color schemes and twelve type sets as independent, freely composable axes.
  • Hand-designed dark mode for every scheme, with the two-ink split that keeps borders quiet and type legible.
  • Full RTL through logical properties — no second stylesheet.
  • Live customizer with light/dark/system, direction, three sidebar styles and fluid/boxed width, all persisted under versioned at: storage.
  • ⌘K command palette, breadcrumb and active-trail engine, all driven by one nav manifest.
  • ApexCharts wrapper with token-driven palettes and live re-theming; Leaflet maps; an inline-SVG icon registry.

Credits

Atelier stands on a lot of excellent open-source work. Thank you to the maintainers and communities behind everything listed here.

Fonts

All typefaces are served through Google Fonts, under the SIL Open Font License:

  • Display faces — Fraunces, Archivo, Bricolage Grotesque, Sora, Outfit, Saira, Antonio, Oxanium, Playfair Display, Chivo, Zilla Slab, Gabarito.
  • Body faces — Libre Franklin, Instrument Sans, Manrope, Figtree, IBM Plex Sans, Public Sans, DM Sans, Mulish, Plus Jakarta Sans.
  • Monospace — JetBrains Mono, constant across all twelve type sets.

Icons

Icon path data follows the Lucide / Tabler Icons convention (24×24 viewBox, currentColor stroke). Both are MIT-licensed. Icons are inlined as SVG — there is no icon font or icon package to install.

Libraries

  • Tailwind CSS v4 — the utility layer the design system bridges into. MIT.
  • ApexCharts — every chart. MIT.
  • Leaflet — the interactive map. BSD-2-Clause. Default tiles from OpenStreetMap contributors, ODbL.
  • Floating UI — dropdown, tooltip and popover positioning. MIT.
  • React and React DOM, and React Router — the application and its routing. MIT.
  • Vite and @vitejs/plugin-react — the dev server and build. MIT.
  • TypeScript — Apache-2.0.

Each project is distributed under its own license; refer to the respective projects for the full terms.

Demo media

The photographs, avatars and illustrations shown throughout the template are included for preview purposes only and are not licensed for redistribution in your own product. Replace them with your own assets, or media you hold the rights to, before deploying to production.

License

Atelier itself is licensed through Envato under the Regular or Extended license you purchased. In short: the Regular license covers one end product that is not sold to end users; the Extended license covers one end product that is. The authoritative terms are on your ThemeForest download page and at themeforest.net/licenses.

Thank you again to every author and contributor whose work makes Atelier possible — and thank you for choosing it.