> ## Documentation Index
> Fetch the complete documentation index at: https://xanots.docs.xano.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The scaffolded project

> What `xanots init` writes, the two frontend presets, theming, add-ons, and the SvelteKit prerendering rules.

## init flags and add-ons

`init` flags: `--framework <react|svelte>` (default: `react`), `--name <name>`
(default: the folder name), `--theme <id>` / `--radius <len>` / `--dark <mode>` /
`--font <id>` / `--font-mono <id>` / `--font-heading <id>` / `--icons <id>`
(the look — see [Theming](#theming)), `--ai <claude|codex|cursor|none>`
(repeatable; writes `CLAUDE.md`/`AGENTS.md`/Cursor rules — none by default),
`--marketplace <pkg>` (repeatable, comma-separated; installs add-ons and
registers them — below), `--force` (scaffold into a non-empty folder),
`--no-install` (skip `npm install`), `--web` (choose all of the above in a
browser instead — see [Choosing in a browser](#choosing-in-a-browser)),
`--from <source>` (fill `xano/` from an existing backend rather than the
starter — see [Pulling an existing workspace](/guides/codegen)).
In a terminal, `init` prompts for the framework and the AI files; every prompt
has a default, so pressing enter twice is a valid answer. The look is never
prompted for — it comes from the flags above, or from `--web`.
The starter backend is empty but already compiles and deploys — grow it from the
walkthrough in `xano/EXAMPLE.md`.

A scaffold ships `@xanots/sdk` and nothing else from the `@xanots` scope. Add-ons
install on demand:

```bash theme={null}
xanots marketplace list                      # every published add-on
xanots marketplace search auth               # …or narrow by keyword
xanots marketplace details @xanots/auth      # what it installs + how to register it
xanots marketplace install @xanots/auth      # add it to the project you're in
```

The three read verbs hit a public catalogue, so they work before you log in.
Every add-on is optional and none is assumed by anything in the scaffold —
install one when you need it.

`init` takes the same package names, so a project can be scaffolded with its
add-ons already wired:

```bash theme={null}
xanots init my-app --framework react --marketplace @xanots/auth,@xanots/vector
```

That installs each package **and** writes its registration into `xano/index.ts`.
Installing alone would leave dependencies nothing imports — a different project
wearing the same name. A module declares how it registers in its own
`package.json` (`"xanots": { "register": "registerAuth" }`); one that has not
adopted the field is read for a single `register*` export, and two of those
without the field is a refusal rather than a guess.

`@xanots/auth` is **authentication, not authorization** — user/login/signup
tables and the endpoints over them. It ships no roles, permissions, or route
guards, so it is not the RBAC answer. Build role guards natively: a role column
on the auth table, then a `s.precondition` on each endpoint gating
`auth("role")`.

`details` is the one to reach for when wiring an add-on: it prints the objects
the add-on puts on your workspace, what you have to supply, and the
`xano/index.ts` registration to copy. Piped, it emits JSON; `--prompt` emits
instructions written to be handed straight to a coding agent.

That is `npm install` with two additions: add-ons are discoverable from `xanots --help`, and the command refuses before npm runs when you are not standing in a
project — the mistake npm answers by silently writing to the wrong `package.json`.
The package name is passed through exactly as typed, so version specifiers, tags,
and third-party packages all work.

## Choosing in a browser

`--web` collects the same choices against a live preview instead of on the
command line:

```bash theme={null}
xanots init my-app --web
```

It is a launcher, not a second scaffolder: a configurator is downloaded on
demand, serves a local page, and finishes by running `init` with the flags your
choices imply — printing the equivalent command so the project stays
reproducible from a script. Everything after `--web` is passed to it untouched,
including `--help`, which is why that one form reaches the network. `xanots help
init` stays offline, like `init` itself.

Because the configurator is fetched at run time, `--web` needs the npm registry
before it can start. `init` on its own reaches out only to install the new
project's dependencies, which `--no-install` skips.

## The frontend preset

To point `npm run dev` at a real backend, copy `.env.example` to `.env.local` — both
live at the **project root**, next to `vite.config.ts` — and set `VITE_XANO_HOST` to a
deployed URL. Deployed builds don't need it: `xanots deploy <entry> --static <dir>` injects the
backend URL as `window.XANO_HOST`, which takes precedence.

The frontend ships `Button` and `Card` already vendored, plus a pre-configured
`components.json`, so `npx shadcn@latest add dialog form input` (or
`npx shadcn-svelte@latest add …` on a Svelte scaffold) works immediately — no
`init` step for either CLI. Components are copied into your repo rather than
installed, so you own and edit them directly. [Lucide](https://lucide.dev/icons)
is installed on both scaffolds — `lucide-react` on React, `@lucide/svelte` on
Svelte — and the landing page already uses it.

## Theming

shadcn components carry no colors of their own: they are Tailwind utilities over
a fixed set of semantic tokens (`--primary`, `--muted-foreground`, `--border`,
the `--chart-*` ramp, the `--sidebar-*` set). Those tokens live at the top of
`frontend/src/index.css`, which is the whole theme — Tailwind v4 keeps it in CSS,
and there is no `tailwind.config.js`. That one stylesheet backs both frameworks.

`init` renders it from a theme you choose, using shadcn's own two-part model:

```bash theme={null}
xanots init my-app --theme zinc-blue     # a base color, plus an accent over it
xanots init my-app --theme stone         # a base color alone
xanots init my-app --theme zinc --radius 0
```

Base colors — the full token set: `neutral` (default), `stone`, `zinc`, `mauve`,
`olive`, `mist`, `taupe`. Accents — a partial override of `primary`, `secondary`,
the chart ramp, and the sidebar primary: `amber`, `blue`, `cyan`, `emerald`,
`fuchsia`, `green`, `indigo`, `lime`, `orange`, `pink`, `purple`, `red`, `rose`,
`sky`, `teal`, `violet`, `yellow`. The values are shadcn's, verbatim, so
`--theme zinc-blue` is what ui.shadcn.com hands out for the same pair.

`--theme` also takes any shadcn **registry theme** — its own, a third-party
generator's, or your team's:

```bash theme={null}
xanots init my-app --theme https://ui.shadcn.com/r/themes/slate.json
xanots init my-app --theme ./brand-theme.json
```

`--radius <len>` overrides the corner radius (a bare number is rem). Everything
else about the project is identical whichever theme you pick, and you can change
your mind later by editing the token values — or by applying another theme over
them with `npx shadcn@latest add <registry-theme-url>`.

### Dark mode

Every theme ships a complete dark palette. `--dark` decides what turns it on:

* `system` (default) — an inline script in the HTML entry applies the OS setting
  before first paint, so the page never flashes light first. No UI.
* `toggle` — that, plus `frontend/src/lib/theme.ts` (the persisted mode) and a
  mode toggle on the landing page cycling system → light → dark.
* `off` — light only. The `.dark` block is still there and still complete.

Whichever you pick, style with the token classes (`bg-primary`,
`text-muted-foreground`) rather than raw palette classes like `bg-gray-100`:
raw ones ignore the theme and are unreadable in dark mode. The scaffolded AI
instruction files say so too.

### Typefaces and icons

Fonts are opt-in and **self-hosted**: each choice installs an `@fontsource`
package rather than linking Google's CDN, which would be a third-party request
on every page load of your app, a failure behind a firewall, and a privacy
question someone inherits later.

```bash theme={null}
xanots init my-app --font geist --font-heading instrument-serif --icons tabler
```

* `--font <id>` — body text (Tailwind's `--font-sans`, which v4 also uses as the
  page default). Sans faces only: `geist`, `inter`, `figtree`, `manrope`,
  `dm-sans`, `space-grotesk`, `outfit`, and 10 more.
* `--font-mono <id>` — code. `jetbrains-mono` or `geist-mono`.
* `--font-heading <id>` — headings. Accepts **any** face, sans or serif, since a
  display serif over a sans body is the reason the slot exists. It emits a
  base-layer rule, so headings pick it up without touching every `<h1>`.
* Omit a slot and it keeps Tailwind's default stack, installing nothing for it.

An unknown id fails at `init` naming the slot it was resolving, so `--font-mono
inter` is an error rather than a proportional face quietly rendering your code
blocks.

`--icons <id>` picks the icon set: `lucide` (default), `tabler`, or `phosphor`.
The binding covers the dark-mode toggle's icons as well as the landing page's —
without that, `--icons tabler --dark toggle` would emit a toggle importing a
library the project no longer installs, a build failure from a flag with nothing
to do with dark mode.

> On a Svelte scaffold `npm run typecheck` runs `svelte-kit sync && svelte-check`
> rather than `tsc`. It checks the backend and the components together — `tsc`
> cannot read `.svelte` files at all.

> **The SvelteKit scaffold prerenders every route.** `frontend/src/routes/+layout.ts`
> sets `prerender = true`, so each route becomes its own HTML document at build
> time and loads as a real page. Pages live in `frontend/src/routes/`, and `files`
> in the `sveltekit()` plugin config keeps the project single-rooted with `xano/`
> as a peer. That config lives in `vite.config.ts` — there is no
> `svelte.config.js`, matching where SvelteKit's own scaffold now puts it.
>
> There is still no server at runtime — Xano is the backend and `deploy --static`
> ships to a host with no runtime, so `+page.server.ts`, form actions, and server
> `load` have nothing to run on, and the build does not stop you. Treat them as
> unavailable rather than trusting a green build.
>
> Two things follow from prerendering. Because it renders at build time,
> module-scope `window`/`document` access fails the **build** rather than the
> browser — use `onMount`, or guard with `browser`. And a dynamic route like
> `/posts/[id]` **fails the build** unless it declares which ids exist:
>
> ```ts theme={null}
> // frontend/src/routes/posts/[id]/+page.ts
> export const entries = () => [{ id: "1" }, { id: "2" }];
> ```
>
> That is deliberate — a loud build error beats shipping a page that 404s for
> real users. Unmatched paths get a real 404 from
> `frontend/src/routes/404/+page.svelte`, which prerenders to `404.html`. It has
> to be a route: SvelteKit never prerenders `+error.svelte` to a file, so that
> alone would ship no `404.html` and every unknown path would serve the home page
> with a 200 instead.
>
> One more build-time check comes with prerendering: a hash link to an id that is
> not on the page it renders on — `<a href="#pricing">` with no `id="pricing"` —
> **fails the build**, naming the route and the id. A hash nav in
> `+layout.svelte` is exempt on `/404` only, since that route inherits the layout
> and by definition carries none of the page's sections.
