Viewi

Write your frontend in PHP

Viewi components are PHP classes with HTML templates. PHP renders the page on the server, and the same components run in the browser after it loads.

$ composer require viewi/viewi

Create components. Use them as HTML tags.

Make a Blog component by creating HTML and PHP files with the same name.

That pair of files would be your <Blog /> component.

Combine components to build pages and applications.

Make a package and share with others.

class Blog extends BaseComponent
{
    public BlogModel $blog;
}
Blog.php
<div>
    <Thumbnail blog="$blog" />
    <a href="{$blog->url}">
        <h3>{$blog->title}</h3>
        <p>{$blog->description}</p>
    </a>
    <LikeButton blog="$blog" />
</div>
Blog.html

Templates are fluent and easy to use.

The syntax is a mix of HTML and PHP expressions. The component's properties are available as variables.

Special tags allow you to build lists and conditions, for example, "foreach" or "if".

Use methods as functions to render computed expressions. Like "heading()".

class BlogsSection extends BaseComponent
{
    public array $blogs;
    public string $emptyHeading = 'No posts in here';

    public function heading()
    {
        $heading = $this->emptyHeading;
        $total = count($this->blogs);
        if ($total > 0) {
            $noun = $total > 1 ? 'Posts' : 'Post';
            $heading = "$total $noun";
        }
        return $heading;
    }
}
BlogList.php
<section>
    <h2>{heading()}</h2>
    <Blog foreach="$blogs as $blog" blog="$blog" />
</section>
BlogList.php

Your components are reactive automatically.

Interactivity is an important part of any modern application.

Use event binders to respond to the user's actions. And Viewi will update the page with new data.

For example, (change)="search" will call the "search" method every time the user types something in the field.

class SearchableBlogList extends BaseComponent
{
    public string $title = "News and articles";
    public array $blogs;
    public array $foundBlogs = [];
    public string $searchText = '';

    public function __construct(
        private BlogService $blogService
    ) {}

    public function init()
    {
        $this->watch('blogs', function () {
            $this->searchText = '';
            $this->search();
        });
    }

    public function mounted()
    {
        $this->search();
    }

    public function search()
    {
        $this->foundBlogs = $this->blogService->filter(
            $this->blogs,
            $this->searchText
        );
    }
}
SearchableBlogList.php
<h3>$title</h3>
<p>Here is something that might interest you.</p>
<SearchInput model="$searchText" (change)="search" />
<BlogsSection 
    blogs="$foundBlogs" 
    emptyHeading="{"No matches for '$searchText'"}" 
/>
SearchableBlogList.php

Integrate with API or backend.

Use HTTP client to get the data.

Assign your page to a specific URL using a built-in router.

Framework agnostic. Make integration with the backend framework of your choice.

class PopularBlogs extends BaseComponent
{
    public ?array $blogs = null;
    public string $category = 'today';

    public function __construct(
        private HttpClient $http
    ) {}

    public function init()
    {
        $this->getPosts();
    }

    public function getPosts()
    {
        $this->blogs = null;
        $this->http
            ->get("/api/blogs/{$this->category}")
            ->then(fn($blogs) => $this->blogs = $blogs);
    }

    public function open(string $category)
    {
        $this->category = $category;
        $this->getPosts();
    }

    public function getTitle()
    {
        return ($this->category === 'today'
            ? 'Today\'s' : 'This week\'s') . ' most popular posts';
    }
}
PopularBlogs.php
<BlogLayout>    
    <div>
        <button (click)="open('today')">Today</button>
        <button (click)="open('week')">This Week</button>
    </div>
    <Loading loading="{!$blogs}">
        <SearchableBlogList
            title="{getTitle()}"
            blogs="$blogs"
        />
    </Loading>
</BlogLayout>
PopularBlogs.php
$router->get('/most-popular', PopularBlogs::class);
routes.php

Discover the source code here

One language

Components, models and validation rules are PHP classes. The same code runs on the server and in the browser, so there is no second copy in JavaScript.

Read more

Rendered by PHP

PHP sends the full page, so it opens with content and search engines read it as is. The browser picks it up from there. Node is only needed for the build.

How it works

Tested against PHP

324 PHP functions have a browser version. Each one is tested against real PHP, and the differences a browser can't avoid are listed with a workaround.

See the list

VS Code extension

Autocomplete for component tags, props, properties and methods in your templates, and Go to Definition from a template to the PHP behind it.

VS Code suggesting the props of a Viewi Accordion component while typing its tag

From the blog

All posts

Viewi UI: the components behind Urlicer's folders

Tree view, tree picker, dropdown menu, breadcrumb, row selection, drag and drop and tables that fit a phone. What each one does, shown on the screen it was written for.

What's new in Viewi 2.8 and 2.9

PHP functions now behave the same on the server and in the browser, and a test suite checks it. Plus event modifiers, guards with arguments, and a few smaller fixes.

Viewi in production

Notes from the person who wrote it, after years of running sites on it: what the design buys you, and what it asks in return.

In production

Urlicer is branded short links, QR codes, team workspaces and click analytics. It runs its entire frontend on Viewi: server-rendered PHP components hydrated in the browser, with no JavaScript framework.

Built and maintained by Viewi's author. Also on Viewi: nutriendoua.com, and this site.

Available for PHP, Swoole and front-end work: contact@viewi.net