Composable admin-UI building blocks. Mounts on top of @verevoir/storage and @verevoir/editor to give you a working content admin — shell, sidebar, document list, document editor, sections editor, tag scheduler — with zero opinion about routing, auth, or deployment target.
npm install @verevoir/admin @verevoir/editor @verevoir/schema @verevoir/storageAdminLayout— persistent React tree that wraps every admin page. Owns the sidebar, context providers, and a slot for page content. Lets consumers compose SPA-style navigation with the sidebar mounted once.AdminShell— chrome (header, breadcrumbs, identity display, body container). Used byAdminLayoutor directly if you want a simpler shell.AdminSidebar— categorised navigation of block types, with an inline filter, collapse state persisted via preferences context, and singleton vs collection handling.
AdminProvider— shares groups, base path, current path, and filter text across every admin component below it.AdminPreferencesProvider— persists per-user admin preferences (sidebar collapsed, expanded type IDs) via localStorage.useAdminGroups,useAdminBasePath,useAdminCurrentPath,useAdminFilter,useFilteredGroups,useSidebarOpen,useExpandedTypes— typed hooks for everything the providers expose.
DocumentList— filtered, grouped listing of documents per block type with edit links.DocumentEditor— the full editor: metadata form (via@verevoir/editor'sBlockEditor), polymorphic sections, optional live preview iframe, save flow, auto-save for structural changes.SectionsEditor— polymorphic page-section editor with drag-and-drop reordering and stacked move-up / move-down controls.
TagList— listing of every tag in use across the registered block types with counts.TagScheduler— bulk-edit form: setpublishFrom/publishToacross every doc carrying a tag. Denied docs (wherecanEditreturns false) are shown greyed out with a lock indicator.
createSaveHandler— framework-agnostic function. Validates the request, merges with the existing document, writes through yourStorageAdapter.createAstroSaveRoute(@verevoir/admin/astro) — thin Astro wrapper around the save handler.
loadAdminGroups({ storage, blocks })— serialisable view model for the sidebar: one group per block type with its docs. Pass the result intoAdminLayout'sgroupsprop.loadTagsSummary({ storage, blocks })— every tag in use across registered block types with counts + which types it spans.loadTagDetail({ storage, blocks, tag })— every doc carrying the given tag, with title + publish window.
Import only what you need; the server subpath stays out of the client bundle.
---
// src/pages/admin.astro
import { loadAdminGroups } from '@verevoir/admin/server';
import '@verevoir/admin/styles/admin.css';
import { storage } from '@/storage';
import { blocks } from '@/schema/registry';
import { AdminHomeIsland } from '@/admin/AdminHomeIsland';
export const prerender = false;
const groups = await loadAdminGroups({ storage, blocks });
---
<html lang="en">
<body>
<AdminHomeIsland client:only="react" groups={groups} basePath="/admin" currentPath={Astro.url.pathname} />
</body>
</html>// src/admin/AdminHomeIsland.tsx
import { AdminLayout, DocumentList } from '@verevoir/admin';
import type { AdminGroup } from '@verevoir/admin';
export function AdminHomeIsland({ groups, basePath, currentPath }) {
return (
<AdminLayout
groups={groups}
basePath={basePath}
currentPath={currentPath}
shell={{ title: 'My Admin' }}
>
<DocumentList />
</AdminLayout>
);
}// src/pages/api/admin/save.ts
import { createAstroSaveRoute } from '@verevoir/admin/astro';
import { storage } from '@/storage';
import { blocks } from '@/schema/registry';
export const prerender = false;
export const POST = createAstroSaveRoute({ storage, blocks });A full working consumer: Verevoir starter.
AdminLayout expects a registry of block types in a specific shape:
import type { BlockRegistry } from '@verevoir/admin';
export const blocks: BlockRegistry = {
page: {
block: pageBlockDefinition,
label: 'Pages',
category: 'Content',
preview: (data) => `/${data.slug ?? ''}`,
},
siteConfig: {
block: siteConfigBlockDefinition,
label: 'Site config',
category: 'Configuration',
singleton: true,
},
};category groups block types in the sidebar (preserves order-of-first-appearance). preview returns a public URL; when set, the editor shows a side-by-side preview iframe. singleton: true for single-instance block types like site config.
Fully themable via CSS custom properties. Override any of these on :root or the .verevoir-admin selector:
| Variable | Default | What it controls |
|---|---|---|
--admin-bg |
#f8fafc |
Page background |
--admin-surface |
#ffffff |
Cards, forms |
--admin-text |
#0f172a |
Body text |
--admin-text-muted |
#64748b |
Secondary text |
--admin-accent |
#ffae9c |
Buttons, focus rings |
--admin-radius |
0.375rem |
Border radius |
--admin-font |
Mulish, system-ui, ... |
Font family |
--admin-content-width |
64rem |
Max content width |
Full list in src/styles/admin.css. Every element exposes data-* attributes for granular CSS targeting — no class names to lock onto.
The starter ships an alternative dark "glass" theme; see its src/styles/admin-theme.css for what a substantial override looks like.
- Routing-agnostic. Each admin route in your host framework (Astro, Next.js, Remix, Vite + React Router) renders an
AdminLayoutwith the appropriate content. The toolkit doesn't ship its own router. - Auth-agnostic. Configure auth at the route level (middleware) and pass an
identityprop toAdminShell. Components likeTagScheduleracceptcanEditcallbacks so the host owns the policy check.@verevoir/accesspairs naturally but isn't required. - Storage-agnostic. Any
StorageAdapterworks. Components take data as props and call consumer-provided save handlers; the toolkit never touches storage directly. - Composable. You can use
AdminShell+DocumentListalone for a minimal admin, or bolt inDocumentEditor+SectionsEditor+TagScheduleras your content model grows.
The Verevoir starter wires every component from this package into a working Astro admin with auth, tag scheduling, and a glass theme.
MIT