Installation

Getting started

Installation Guide

Official guide to install SERIXA, render your first page, and use widgets, themes, and layouts.

Requirements: PHP 8.2+ · Composer

Related: Getting started · First blog · CLI · Troubleshooting


Installing via Composer

Option A — Scaffold with the CLI (recommended)

From a clone of this monorepo:

cd packages/cli
composer install
php bin/serixa new my-app
cd my-app
composer install
php -S localhost:8000 -t public

Or, once serixa/cli is available as a dependency:

composer require --dev serixa/cli
vendor/bin/serixa new my-app

Generate apps outside the framework tree (sibling folders). Path repositories that nest inside framework/ can recurse — see Path-repository recursion.

Option B — Add the framework to an existing project

composer require serixa/framework

Optional scaffolding tooling:

composer require --dev serixa/cli

Option C — Path repository (local monorepo development)

Until you publish to Packagist, point Composer at the local framework/ folder:

{
    "repositories": [
        {
            "type": "path",
            "url": "../path/to/Serixa/framework",
            "options": { "symlink": true }
        }
    ],
    "require": {
        "php": "^8.2",
        "serixa/framework": "*@dev"
    }
}

Then:

composer update serixa/framework

Confirm the junction targets framework/, not the monorepo root. A wrong path surfaces as Class "Serixa\Component\…" not found.

Verify the install

php -r "require 'vendor/autoload.php'; echo class_exists(Serixa\\Rendering\\Renderer::class) ? 'OK' : 'FAIL';"

First page

Create public/index.php (or edit the file generated by serixa new):

<?php

declare(strict_types=1);

require dirname(__DIR__) . '/vendor/autoload.php';

use Serixa\Component\Button;
use Serixa\Assets\StyleSheet;
use Serixa\Component\Text;
use Serixa\Document\Document;
use Serixa\Rendering\Renderer;
use Serixa\Runtime\ClientRuntime;

$document = Document::make()
    ->title('Hello SERIXA')
    ->description('My first page')
    ->body(
        Text::make('Hello, SERIXA')->as('h1')->size('xl')->weight('bold'),
        Text::make('Write PHP. Get semantic HTML.')->tone('muted'),
        Button::make('Continue')->primary(),
    );

StyleSheet::register($document->assets());
ClientRuntime::register($document->assets());

header('Content-Type: text/html; charset=utf-8');
echo (new Renderer())->render($document);

Serve from public/ (run serixa build or composer build first so /assets/serixa.css exists):

vendor/bin/serixa build
php -S localhost:8000 -t public

Open http://localhost:8000. The document root must be public/ so assets resolve correctly.


Rendering

SERIXA is server-side only. Renderer turns a widget tree or a full Document into an HTML string.

Fragment (partial HTML)

use Serixa\Component\Button;
use Serixa\Rendering\Renderer;

$html = (new Renderer())->render(
    Button::make('Save')->primary(),
);

Use fragments inside controllers, existing templates, or email bodies.

Full page (Document)

use Serixa\Document\Document;
use Serixa\Rendering\Renderer;

$document = Document::make()
    ->title('Dashboard')
    ->description('Admin')
    ->language('en')
    ->body(/* widgets */);

echo (new Renderer())->render($document);

Document owns <title>, meta tags, favicon, Open Graph fields, and registered CSS/JS via $document->assets().

Interactive widgets (optional)

If you use Modal, Dropdown, Tabs, Toast, and similar, register the client runtime:

use Serixa\Runtime\ClientRuntime;

ClientRuntime::register($document->assets(), '/assets/runtime/serixa.js');

serixa new copies serixa.js into public/assets/runtime/. Serving from anywhere except public/ causes a 404 — see Runtime script 404.


Widgets

Widgets are PHP objects under Serixa\Component\. Build them with ::make() and fluent modifiers:

use Serixa\Component\Button;
use Serixa\Component\Card;
use Serixa\Component\Column;
use Serixa\Component\Text;

Column::make([
    Text::make('Welcome')->as('h1')->size('xl')->weight('bold'),
    Card::make()
        ->title('Getting started')
        ->child(Text::make('Compose widgets. SERIXA renders HTML.')->tone('muted')),
    Button::make('Save')->primary()->large(),
]);
AreaExamplesDocs
LayoutRow, Column, Center, Padding, Scaffoldlayout-engine.md
ContentText, Button, Card, Link, Containerui-foundation.md
FormsForm, Input, Label, Checkboxui-foundation.md
DataDataTable, Pagination, Badge, StatCarddata-display.md
InteractiveModal, Tabs, Dropdown, Toastinteractive-components.md

Widgets do not hardcode CSS classes. Variants like ->primary() and ->large() become theme state; the theme maps that state to Tailwind utilities.


Themes

DefaultTheme maps (component, state) → Tailwind class lists. Widgets resolve styles through ThemeManager during render.

use Serixa\Theme\DefaultTheme;
use Serixa\Theme\ThemeManager;

// Default — applied automatically
ThemeManager::current(); // DefaultTheme

// Swap for the whole request
ThemeManager::set(new DefaultTheme());

Inspect a mapping:

$classes = ThemeManager::current()->classes('button', [
    'variant' => 'primary',
    'size' => 'lg',
]);

Custom themes

  1. Scaffold: serixa make:theme Dark
  2. Implement Serixa\Contracts\ThemeInterface (or extend DefaultTheme and override maps)
  3. Call ThemeManager::set(new App\Themes\DarkTheme()) before render

Unknown component keys or invalid variants throw InvalidArgumentException — intentional fail-fast. See Unknown theme state.

Theme classes still need CSS in the browser: run serixa build to emit /assets/serixa.css offline (assets.md).


Layouts

Composition primitives

use Serixa\Component\Center;
use Serixa\Component\Column;
use Serixa\Component\SizedBox;
use Serixa\Component\Text;

Center::make(
    Column::make([
        Text::make('Welcome')->as('h1'),
        SizedBox::height(4),
        Text::make('Centered column layout.')->tone('muted'),
    ])->gap(2)->alignCenter(),
);

Application shell (Scaffold)

use Serixa\Component\AppBar;
use Serixa\Component\Footer;
use Serixa\Component\Link;
use Serixa\Component\MainContent;
use Serixa\Component\Scaffold;
use Serixa\Component\Sidebar;
use Serixa\Component\Text;

Scaffold::make()
    ->appBar(AppBar::make()->title('Dashboard'))
    ->sidebar(
        Sidebar::make(
            Link::make('Home')->href('/')->active(),
            Link::make('Settings')->href('/?page=settings'),
        ),
    )
    ->body(
        MainContent::make(
            Text::make('Page content')->as('h1')->size('xl')->weight('bold'),
        ),
    )
    ->footer(Footer::make('© 2026'));

Scaffold emits landmarks (<header>, <aside>, <main>, <footer>). Prefer wrapping page bodies in a shared layout class (as serixa new does with layouts/AppLayout.php) so chrome stays consistent across routes.

SERIXA does not include a router — wire pages yourself (typically match ($_GET['page']) in public/index.php). See first-blog.md.


Example

Minimal end-to-end app: install, one page, layout, offline CSS build, render.

composer.json

{
    "name": "app/hello-serixa",
    "require": {
        "php": "^8.2",
        "serixa/framework": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

Use a path repository while developing against this monorepo.

public/index.php

<?php

declare(strict_types=1);

require dirname(__DIR__) . '/vendor/autoload.php';

use Serixa\Component\AppBar;
use Serixa\Component\Button;
use Serixa\Component\Card;
use Serixa\Component\Column;
use Serixa\Component\Footer;
use Serixa\Component\MainContent;
use Serixa\Component\Scaffold;
use Serixa\Component\Text;
use Serixa\Document\Document;
use Serixa\Rendering\Renderer;

$shell = Scaffold::make()
    ->appBar(AppBar::make()->title('Hello'))
    ->body(
        MainContent::make(
            Column::make([
                Text::make('Installed successfully')->as('h1')->size('xl')->weight('bold'),
                Card::make()
                    ->title('Next steps')
                    ->child(Text::make('Add pages, a layout class, and your own routing.')->tone('muted'))
                    ->child(Button::make('Continue')->primary()),
            ])->gap(4),
        ),
    )
    ->footer(Footer::make('SERIXA'));

$document = Document::make()
    ->title('Hello · SERIXA')
    ->body($shell);

StyleSheet::register($document->assets());
ClientRuntime::register($document->assets());

header('Content-Type: text/html; charset=utf-8');
echo (new Renderer())->render($document);
composer install
vendor/bin/serixa build
php -S localhost:8000 -t public

Best practices

  1. Serve from public/ — keep vendor/, pages, and config outside the document root.
  2. Compose, don't concatenate HTML — build UI with widgets; let Renderer produce markup.
  3. One layout, many pages — shared Scaffold / AppLayout for chrome; page classes return Document.
  4. Theme owns styling — avoid raw Tailwind class strings on widgets when a fluent API exists (->primary(), ->tone('muted')).
  5. Compile CSS offline — run serixa build so /assets/serixa.css exists; see styling.md and deployment.md.
  6. Register ClientRuntime only when needed — static pages need zero JavaScript.
  7. Keep apps outside framework/ when using path repositories.
  8. Own routing and auth — SERIXA renders UI; sessions, validation, and URLs are your app's responsibility.
  9. Prefer public APIsSerixa\Component\*, Document, Renderer, ThemeManager. Avoid @internal types.
  10. Run QA in packages you maintaincomposer qa (PHPUnit + PHPStan + PHP-CS-Fixer) before releasing.

Troubleshooting

SymptomLikely causeFix
Class "Serixa\…" not foundAutoload / wrong path junctioncomposer dump-autoload; ensure path repo URL is …/framework
Interactive widgets do nothingMissing or 404 serixa.jsServe -t public; check runtime script 404
No styles / unstyled pageMissing compiled CSSRun serixa build; register StyleSheet::register()/assets/serixa.css
Unknown theme componentTypo or unsupported variantUse allowed variants/sizes; see unknown theme state
composer install hangs in monorepoNested path recursionGenerate apps as siblings; path-repository recursion
Package not found on PackagistLocal-only / unpublishedUse a path repository against this clone
PE onClick attributes ignoredAttributes only — no handlerRegister Serixa.on('name', …) after the runtime loads

Full reference: troubleshooting.md · faq.md


Next steps

GoalDoc
Mental model & scopegetting-started.md
30-minute blogfirst-blog.md
Admin shellfirst-dashboard.md
Deploydeployment.md
CLI commandscli.md