# Component kit: tables, forms, charts and boards, ready to place
Source: https://sovaria.in/docs/component-kit

Data-bound tables, forms, charts, boards, drawers, stats and sign-in screens every app starts with, plus page headers, heroes, illustrations, credited photos and motion that follow your design direction.

Most screens in an app are the same few shapes: a list of rows, a form to create or change one, a chart of how things went, a board of things by status. Every app made from the starter or a gallery template has them ready in `src/components/kit`, connected to your tables through `db.from` so your access rules decide what each person sees and may change. Sovariq places and configures them rather than writing each screen from scratch, and you can do the same in the Code tab.

| Component | What it gives you |
|---|---|
| `SovariaTable` | One of your tables, paged on the server: sort by any column, search, a filter menu per column with fixed values, inline edits, row selection with bulk actions (Delete asks first), show or hide columns, three row heights, CSV export of everything that matches, loading, empty and error states, and cards instead of a table on phones. |
| `DataTable` | The same for rows you already have (a report, a joined query, another API), done in the browser. |
| `RecordForm`, `Form` | Create or edit a row with labels, required fields, email and link checks, number ranges, lengths, your own rules, messages under each field and focus on the first problem. An edit saves only what changed. |
| `Chart`, `groupRows` | Line, area, bar, stacked bar, pie and donut charts in your theme's chart colours; `groupRows` turns rows into points (count, sum, average, min, max by day, week, month, year or value). |
| `SovariaBoard` | A kanban of a table's rows by a status column that always fits the page: every stage side by side (more than six wrap into two rows), one stage at a time with a switcher on phones. Drag a card with the mouse, or use its Move menu (touch and keyboard), and the change is saved at once; a move your access rules refuse goes back and says why. |
| `DetailDrawer` | A side panel with a row's details that turns into its edit form. |
| `StatGrid`, `SovariaStat` | A row of figures, each a count from your tables. |
| `AuthCard` | Sign-in and sign-up screens (password, your sign-in providers, magic links, two-step codes) centered, beside a brand panel, or plain; pass your own form as its children to keep it. |

## A list page in a few lines

src/pages/Orders.tsx:
```ts
import { useState } from 'react';
import { DetailDrawer, SovariaTable } from '@/components/kit';

type Order = { id: string; number: string; customer_email: string; status: string; total: number; created_at: string };

export default function Orders() {
  const [open, setOpen] = useState<Order | null>(null);
  return (
    <section className="mx-auto max-w-6xl px-5 py-10">
      <SovariaTable<Order> table="orders" title="Orders" allowDelete onRowClick={setOpen}
        defaultSort={{ key: 'created_at', desc: true }}
        columns={[
          { key: 'number', header: 'Order' },
          { key: 'customer_email', header: 'Customer', type: 'email' },
          { key: 'status', type: 'badge', options: ['new', 'paid', 'shipped'], editable: true },
          { key: 'total', type: 'money', cents: true },
          { key: 'created_at', header: 'Placed', type: 'date' },
        ]} />
      <DetailDrawer table="orders" row={open} onClose={() => setOpen(null)} editable
        title={(o) => `Order ${o.number}`}
        fields={[{ key: 'status', options: ['new', 'paid', 'shipped'] }, { key: 'total', type: 'money', cents: true }]} />
    </section>
  );
}
```

Column types: text, longtext, number, money (with a currency, stored in cents or not), percent, date, datetime, boolean, badge (with its values), email, url, image and avatar. A column can show its own content (`cell`) and still sort, search and export by the stored value.

## Forms

Create or edit a product:
```ts
<RecordForm table="products" id={editing?.id} columns={2} variant="card" title="Product"
  fields={[
    { key: 'name', required: true, maxLength: 120 },
    { key: 'price', type: 'money', required: true, min: 0 },
    { key: 'category', options: ['books', 'music', 'games'], required: true },
    { key: 'description', type: 'longtext', span: 2 },
    { key: 'in_stock', type: 'boolean', label: 'In stock' },
  ]}
  onSaved={() => setEditing(null)} onCancel={() => setEditing(null)} />
```

## Charts without SQL

Revenue per day:
```ts
const { rows, loading, problem } = useRows<Order>('orders', (q) => q.gte('created_at', since), [since]);

<Chart title="Revenue" kind="area" format="money" loading={loading} problem={problem}
  data={groupRows(rows, { by: 'created_at', bucket: 'day', value: 'total', op: 'sum' })} />
```

## Pages that look designed

The kit also has the pieces that make a page look made by a design studio rather than a list of plain pages. They follow your app's design direction (the look you pick on the plan card), so the same code gives a dense CRM, a warm bakery site or a bold portfolio.

| Piece | What it does |
|---|---|
| `PageHeader` | The top of an app screen: an icon, the title, one line on what it's for, the main actions, and room for a row of figures or tabs. A link back on detail pages. |
| `Hero` | The first screen of a site page, in the direction's layout: centered, split (text beside a photo or illustration), full-bleed photo, editorial (huge type) or bento. |
| `Section`, `FeatureGrid` | Titled blocks with spacing from the direction's density; features, services or menu items as cards with icons or photos that enter one by one and lift on hover. |
| `Photo`, `PhotoCredits` | A photo with its alt text and the theme's corners. With no photo yet, or one that fails to load, a placeholder in your colours, never a broken image. The footer credits every stock photographer. |
| `Illustration`, `Pattern`, `SpotIcon`, `Monogram` | Twenty illustrations (empty, search, team, chart, calendar, tasks, success, photo, shop, food, map, pipeline, documents, launch, growth and more) drawn in your colours, background patterns (grid, dots, blobs, waves, rings), icons in tinted tiles and initials on a colour for people and companies. Drawn for Sovaria and free to use (CC0). |
| `NewRecordButton` | A "New …" button that opens the create form in a side panel instead of a form left open under the list. |
| `WorkspacePage` | A whole module screen from a short configuration: live figures at the top, then tabs, each a table with search, sorting, filters, paging, bulk and row actions and a **New** form or multi-step wizard, a drag-and-drop board, or anything else. Tabs and figures can be limited to roles. Sovariq builds busy staff screens (students and admissions, invoices and payments, staff and applicants) this way. |
| `EntityPage` | One record's page (drill-down): its name and a link back, figures about it, an Overview of its fields and a tab for each kind of related record — an order's items, payments and messages — each a full table filtered to this record, with New already linked to it. Lists open it from any row. |
| `ChatPanel` | Chat on Sovaria realtime: one-to-one and group conversations with unread counts, messages that arrive live, who is here and who is typing, and files kept in a private bucket. The tables' rules let people see only the conversations they belong to. |
| `SitePage`, `GallerySection`, `StepsSection` | A whole marketing page as a list of sections — hero, logos, numbers, features, bento, gallery, steps, testimonials, pricing, FAQ, call to action or your own — in your direction's look. A new app's home page starts as one with six sections for Sovariq to fill with your words, numbers and photos. |
| `Reveal`, `Stagger`, `Ticker`, `Marquee`, `Parallax` | Entrances, cards that appear one after another, numbers that count up, an endless strip of logos or quotes, and images that drift as the page scrolls. |

Table columns can be `avatar` (a name with its initials on a colour) and `badge` columns give each value its own colour.

## Header, footer and the signed-in portal

Apps don't share one navbar and footer. Your design direction picks the frame: the public site's header (a bar, a centred name, a floating pill, transparent over the hero, or a minimal menu that opens full screen), its footer (simple, columns, a large statement of your name, or none), and the portal your signed-in people use — a sidebar, an icon rail or a top bar with tabs, with a tab bar on phones and no marketing footer. An internal tool like a CRM or ERP is all portal; a shop or clinic with accounts has both. Ask for a different one ("a sidebar", "no footer") and Sovariq sets it.

## Motion

The direction sets a motion level: **none**, **subtle** (the default: short fades as content appears, cards that lift on hover, numbers that count up once) or **expressive** (bigger staggered entrances, parallax and marquees). Ask for an animated or lively app and the plan's looks are expressive; ask for no animation and they are still. Visitors who turn on "reduce motion" on their device always get a still page, and screenshots always show the finished page.

## Photos

Sovariq finds licensed stock photos from Unsplash and Pexels for your pages (`find_images`) and keeps them in `src/photos.ts` with the photographer's name and link; the footer credits them, as their licences ask. Your own photos go in a storage bucket. Apps load images only from their own files, Sovaria storage, Unsplash, Pexels and sign-in avatars (a Content-Security-Policy in `index.html`); the safety check warns about any other image host or a stock photo without its credit, and Sovariq never types image links it found elsewhere. If stock photos aren't set up on your Sovaria, apps get placeholders until you add photos: art drawn in your colours and chosen from the page's own words, so two apps never show the same picture. A build of an app with a public site has a **Photos and illustrations** task, and the finish check warns when `src/photos.ts` is still empty.

Sovariq also adds **illustrations** of people and scenes — someone presenting at a board, reading, working at a desk, a meeting, planning on a calendar, celebrating — copied into your app (`public/illustrations/`) in its own colours, for how-it-works steps, onboarding and empty states. They come from [Open Doodles](https://www.opendoodles.com) by Pablo Stanley and from Sovaria, both CC0: free to use, no credit needed. Icons are one set (lucide), one chosen for each item; the finish check warns about emoji used as icons.

## A portal for each kind of user

A whole-app plan groups the roles that work alike into **portals**, shown on the plan card: each has its own home page (a dashboard made for that group), its own look and a sidebar grouped by work area. Leaders and admins get a figures-first sidebar of 8–15 pages; operations staff a compact work queue; people who work within assigned groups a sidebar around those groups; customers, members and families simple tabs with larger type. Once you approve, Sovaria writes the portals into `src/config.ts` (`site.portals`) and removes the starter pages the plan doesn't keep — Features, Pricing, the demo dashboard and, when accounts are invite-only, the public sign-up page. After sign-in each person lands on their portal's home; the sidebar shows only pages that exist and that their roles may open, and someone in two groups can switch portals. Edit the portal list in `src/config.ts` like any other copy.

Each page task closes only when that page's own file was written: a detail page isn't done because its section's list page was, and the home page isn't done until it is written from scratch without the starter's copy. When a migration fails on an error Sovaria recognises (a function whose parameters changed, a rule that reads its own table, a part already applied), the result says the fix, so the next attempt changes only that.

A big data model goes in parts by module, about ten tables per migration, and every table is created with its row security and policies in the same migration — a migration that creates a table without them is refused, and the data-model task closes only when every table has its rules. Two planned pages with the same name can't share one file (a second "Home" can't replace the public home), and the data hooks are checked as they are written: `useRows('table', (q) => q.eq(…), [deps])`, never the Supabase-style `useRows(() => db.from(…))` or `q.select(…)`, which fail in the browser. A page that shows an error state when Sovariq opens it ("Could not load…", "Something went wrong") doesn't count as checked.

## The full spec, built in stages

For a whole system (portals for several groups, or fifteen pages and more) Sovariq first writes the product spec into `PLAN.md`: under *Spec*, a section for every portal (its home and its sidebar) and for every page — its sections, tables and columns, filters, actions, forms and their fields and checks, figures, drill-downs and empty, loading and error states — then the data, flows, notifications, account and admin settings. Each page task then names its lines (`spec PLAN.md:120-168`), and Sovariq reads only those lines while it builds that page, so even a long spec costs little. With *pause after each checkpoint* (or a staged build) the work stops after each stage — spec, backend, pages, wiring, checks, polish — for you to look and say *continue*; Sovariq asks when something it needs is missing and suggests a default.

Finished means checked: before the checks stage closes, Sovariq opens every page as a role that may open it, detail pages (`/orders/:id`) included. A detail page counts only when it is opened with a real record: its own *not found* page proves nothing. A page that shows different things to different roles is opened as a role from each side. A page that shows an error ("Could not load…", "Something went wrong") becomes a fix task, and closes only when the page opens cleanly — so does a blank page or a figure that reads NaN or undefined. A page that throws shows the error in place instead of blanking the app, and a page whose spec lists features it doesn't have yet (a calendar, a chat, uploads, a wizard, a board, a chart…) gets a follow-up task in the polish stage that finishes them. Tasks close as soon as their work is done, in any order — one migration can finish several. Every portal's sidebar lists every page its roles may open — the ones the portal's groups don't name go in *More* — and the account page has the profile and photo, password, two-step sign-in, sessions and notification choices.

Sovariq doesn't retry blindly. When the same thing fails twice in a row — the data model's migration, the build, one page, finishing one task, or an edit that undoes an earlier one — its next attempt is held until it has read the real state (the schema and rules, the exact lines of the file, the page's logs) and said what the cause is. If it still fails four times in a row, Sovariq skips that task with the reason, carries on with the rest and names it in the report; a task that uses more than its share of the build's budget is stopped the same way.

## Dense pages and the finish check

The finish check (Sovariq runs it before it reports an app as done) also looks for thin pages: a staff list that shows rows without search, sorting, filters or actions; a home page with fewer than five sections or no proof (numbers, testimonials, logos or a gallery); photo slots with an empty `src/photos.ts`; starter pages (Features, Pricing, the demo dashboard) the plan doesn't list; and a public sign-up page when `PLAN.md` says accounts are invite-only — the one error of these. Deleting the `/sign-up` route also removes the header's *Get started* button and the sign-in page's *Create an account* link. Row rules that read each other in a circle (one table's rule checks a second table whose rule checks the first) would fail every read for some people, so Sovariq refuses such a migration and names the circle.

For a real 3D object (a chair, a shoe, a dish), Sovariq can add a CC0 model from Poly Haven (`find_models`, or the `fe_add_model` tool): Sovaria downloads it from Poly Haven only, compresses it into one `.glb` (at most 5 MB) and saves `public/models/<name>.glb`, a poster picture beside it and a row in `docs/CREDITS.md` (name, author, source, licence) as one version you can undo. The page shows it with the `model-3d` add-on (`<Model3D src="/models/<name>.glb" poster="/models/<name>.webp" alt="…" />`), which loads it only when it scrolls near and shows the poster on slow or low-memory devices. Your own `.glb` works the same way; Kenney and Quaternius models (also CC0) come in as your upload. The builder preview serves your `public/` files the same way the published site does, so the model shows while you build.

## Look and theme

Each direction also has a **signature**, the one memorable move (giant numerals, a band of your own words moving under the hero, outlined display type, sticky section headings, a slanted edge, your name huge at the foot of the page, colour-block bands, a hand-drawn underline, chapter numbers, an oversize quote mark, a hairline grid or cut-out photo shapes). It also sets a **photo treatment** applied to every photo (natural, duotone in your brand colour, film grain, framed like prints, arched, tilted or black and white), a **section rhythm** (even, tinted bands, or editorial with headings in their own column), how signed-in **homes** are composed (`PortalHome`: figures first, the days ahead, recent activity, or next steps as cards) and the order of the home page's sections. They live in `src/components/kit/look.ts` and `DESIGN.md`, so the same page code looks different in every app; anything you ask for yourself wins.

Before an app is called done, Sovariq runs a **design critique**. Every page it opens in your preview is measured: text contrast, heading hierarchy, tight display type, text touching the edge, the same section shape again and again, links and buttons that do nothing, stray escape codes and placeholder art. A page with a problem gets a fix task, which closes only when that page measures clean. It also screenshots the home and each portal home on desktop and phone and checks them against the design direction and your brief.

Every component takes a `variant` (card, plain, outline, soft or glass) and tables a density. Your app's defaults are one line in `src/components/kit/theme.ts`: variant, density, motion level, hero and header style, background pattern, illustration style (line, duotone or bold) and navigation (top bar or sidebar); applying a design direction writes it, along with a type scale. Colours, corner radius and fonts come from the design tokens in `src/index.css`, so changing your brand or switching to dark mode restyles the whole kit.

> **Access rules still decide:** The kit reads and writes with your project URL and anon key, as the signed-in user. A Delete button or an inline edit only works where your table's rules allow it; when they don't, the person sees a plain "you don't have access" message, never SQL or policy names. The safety check reads which tables a page's kit components use, and what they can do with them, like any other page code.

Apps made from the starter or from any Sovaria gallery template (Keel ERP, Meridian CRM, Stockroom and the rest) have the kit, styled by that template's own theme. Apps made before the kit existed, and apps imported from GitHub or a shadcn block, don't: Sovariq checks first and builds those screens from the components already in the app.
