Search by

rajtik76 / nette-tailwind-starter-kit

rajtik76

Nette starter kit with Tailwind CSS: authentication, dashboard and settings

Package info

github.com/rajtik76/nette-tailwind-starter-kit

Type:project

pkg:composer/rajtik76/nette-tailwind-starter-kit

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-11-04 07:29 UTC

This package is auto-updated.

Last update: 2026-11-04 07:59:47 UTC


README

CI

A Nette web project with authentication, dashboard and account settings on Latte templates and Tailwind CSS 4. Built on the rajtik76/nette-tailwind skeleton.

  • PHP 8.4+, Nette 3.2, Latte 3
  • Tailwind CSS 4 via @tailwindcss/vite, dark mode via the .dark class
  • Vite 8 with @nette/vite-plugin, assets served by nette/assets
  • Nette Forms with client-side validation by nette-forms, Alpine.js for small UI behaviour
  • Nette Database Explorer, SQLite by default
  • Migrations, seeders and factories from rajtik76/nette-migrations
  • Mail through nette/mail with Latte templates, written to log/mail until SMTP is set

Features

  • Log in with remember me, registration, logout
  • Forgotten and reset password, email verification, password confirmation
  • Login and mail rate limiting, one-time tokens for password reset and email verification
  • Dashboard in an application layout with a sidebar, mobile menu and user menu
  • Settings: profile (a new email is verified again), password, appearance (light, dark, system), account deletion
  • Every auth feature can be switched off in config/common.neon

Two-factor authentication and API tokens are not part of the kit yet.

Requirements

  • PHP 8.4 or newer
  • Composer
  • Node.js 22.12+ and npm (.nvmrc pins the version used in CI)

Installation

composer create-project rajtik76/nette-tailwind-starter-kit my-app
cd my-app
npm install
npm run build
php nette migrate --seed

From a clone of the repository:

git clone https://github.com/rajtik76/nette-tailwind-starter-kit.git my-app
cd my-app
composer install
npm install
npm run build
php nette migrate --seed

Log in as test@example.com with the password password. Make sure the temp/ and log/ directories are writable.

npm run build is required. Vite is enabled in config/common.neon, so the page fails with "Failed to read Vite manifest" until the assets are built.

Database

The default connection is SQLite in database/database.sqlite (ignored by git). The file is created on the first connection, so php nette migrate is all it takes. To use MySQL, MariaDB or PostgreSQL, override the connection in config/local.neon:

database:
    dsn: 'mysql:host=127.0.0.1;dbname=app'
    user: root
    password: secret

php nette migrate --seed creates the user test@example.com with the password password.

php nette migrate:fresh --seed      # drop all tables, migrate and seed again
php nette make:migration create_posts_table
php nette make:factory Post
php nette make:seeder Post

Migrations live in database/migrations, factories and seeders in database/factories and database/seeders. See rajtik76/nette-migrations.

Authentication

User accounts live in app/Model/User:

Class Purpose
UserFacade registration, profile update, password change, account deletion
Authenticator Nette\Security\Authenticator; 5 failed logins per minute per email + IP lock the email out
PasswordReset emails a reset link (one per minute) and resets the password
EmailVerification emails a verification link (one per minute) and verifies the email
PasswordConfirmation a confirmed password is valid for 3 hours
UserRepository, PasswordResetRepository, EmailVerificationRepository database access

Reset and verification tokens are random, expire in 60 minutes and only their SHA-256 hash is stored (tables password_resets and email_verifications). A new email must be verified again. The session stores only the user id; Authenticator reloads the user on every request, so a deleted user is logged out. App\Mail\UserMailer sends the mails.

Presenters create forms through App\Presentation\Accessory\FormFactory, which adds the shared name, email and password controls. Nette Forms validate the input (emails are trimmed and lowercased), the model reports domain errors such as a taken email or an invalid token as exceptions, and presenters add them to the form field.

Features can be switched off in config/common.neon:

parameters:
    features:
        registration: true
        resetPasswords: true
        emailVerification: true

New passwords need 8 characters. In production mode (FormFactory(strictPasswords: %productionMode%) in services.neon) they need 12 characters with mixed case, numbers and symbols. A login expires after 2 hours of inactivity, or after 30 days with "Remember me" (AuthPresenter).

Pages and routes

Routes are listed one by one in app/Core/RouterFactory.php. Routes of disabled features are not registered.

Route Presenter Access
/login, /register, /forgot-password, /reset-password/<token> Auth guests
/verify-email, /verify-email/<token> Verification logged in
/confirm-password, /logout (POST) Auth logged in
/dashboard Dashboard verified email
/settings/profile Settings logged in
/settings/appearance Settings verified email
/settings/security Settings verified email, confirmed password

Presenters check access in startup() or action<Action>() with the BasePresenter methods requireLoggedUser(), requireVerifiedEmail(), requireConfirmedPassword() and redirectLoggedUser(). The login and password confirmation pages get the stored request as backlink (storeRequest() / restoreRequest()), so the user returns to the original page. Only GET requests are stored, a POST would submit the form again.

Forms are plain POST forms. A successful submit redirects and the result is shown as a flash message.

Templates use Tailwind utilities directly. Shared pieces are Latte blocks in app/Presentation/@ui.latte (form field, checkbox, auth header, logo) and Heroicons in @icons.latte. The button() Latte function returns the classes of a button variant (primary, danger, filled, ghost). Dropdowns, the mobile sidebar, the delete-account dialog, password visibility and the appearance switch use Alpine.js.

Mail

Out of the box, mails are written to log/mail as .eml files (Nette\Mail\FileMailer), so nothing breaks without an SMTP server. To send them, override the mailer in config/local.neon, e.g. for Mailpit (UI on http://localhost:8025):

services:
    mail.mailer: Nette\Mail\SmtpMailer(host: 127.0.0.1, port: 1025, username: '', password: '')

docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit

In production, set your SMTP server and credentials the same way and change mailFrom and appName. Mails are sent synchronously, so a failing SMTP server is an error (HTTP 500).

App\Mail\TemplateMailer renders app/Mail/templates/<name>.latte inside the shared @layout.latte; the <title> block becomes the subject.

Development

Run the Vite dev server and a PHP server side by side:

npm run dev
php -S localhost:8000 -t www

Then open http://localhost:8000. In debug mode, {asset 'main.js'} loads assets from the Vite dev server, so changes to templates and CSS show up immediately. Without the dev server running, the last npm run build output is used.

Herd / Valet need a custom driver that serves www/ as the document root. wwwDir is set explicitly in app/Bootstrap.php, because these servers boot index.php through their own router script.

Console

php nette is the application console, provided by rajtik76/nette-console:

php nette list                       # all commands
php nette make:command SendReport    # app/Console/SendReportCommand.php, runs as app:send-report
php nette cache:clear                # delete temp/cache
php nette package:discover           # extensions registered by installed packages

Packages declare their DI extensions in composer.json (extra.nette.extensions) and Bootstrap registers them automatically, so their commands work right after composer require. Classes ending with Command in app/ are registered by search in services.neon.

The console runs in debug mode, so config changes apply without clearing the cache. Set NETTE_DEBUG=0 in production.

Checks

composer run lint:check    # Latte templates (own engine, warnings fail) + NEON config
composer run type:check    # PHPStan level 8
composer run tester        # Nette Tester
composer run ci:check      # all of the above, same as GitHub CI

Tests render the layout, so run npm run build first. CI runs on PHP 8.4 and 8.5, Node version comes from .nvmrc.

Testing

tests/config.neon switches the container to an in-memory SQLite database (migrated by tests/bootstrap.php), an ArrayMailer that keeps sent mails, a FrozenClock and a memory cache. Helpers in tests/Support/functions.php:

$container = require __DIR__ . '/../../bootstrap.php';

actingAs($container, createUser($container, ['email_verified_at' => null]));
$response = request($container, 'POST', '/settings/profile?do=profileForm-submit', $post);

redirectPath($response);         // '/settings/profile', without the flash id
html(follow($container, $response));   // the redirect target with its flash message
html($response);                 // rendered page
mailLink(mailer($container)->last());

request() goes through the router and the presenter, so tests cover access control too. tests/Model tests the model, tests/Presentation the pages and forms.

How Tailwind is wired in

  • assets/main.css imports Tailwind and adds @source "../app". Vite's root is assets/, so without it Tailwind would not scan the Latte templates in app/.
  • assets/main.js is the Vite entry point and imports main.css.
  • vite.config.ts registers the tailwindcss() plugin next to the Nette plugin.
  • app/Presentation/@layout.latte renders {asset? 'main.js'}, which outputs both the <script> and the <link rel="stylesheet"> for the bundled CSS.
  • The dark: variant follows the .dark class on <html>. An inline script in @layout.latte sets it before the first paint from localStorage (appearance is light, dark or missing for system); assets/appearance.js changes it at runtime and the Appearance settings page switches it through the appearance Alpine component.

Use Tailwind classes directly in any .latte file under app/.

Welcome and error pages

  • Colors and the font are defined in @theme in assets/main.css. The font (Instrument Sans) is loaded from Bunny Fonts by the welcome page and the auth and app layouts, through the head block of @layout.latte. Error pages make no external requests and fall back to the system font.
  • The cube illustrations are inline SVG blocks in app/Presentation/@cubes.latte.
  • Error4xx/@layout.latte wraps every 4xx template in a shared frame, so 403.latte, 404.latte etc. stay plain markup.
  • 500.phtml and 503.phtml use neither Tailwind nor the layout, because they must render even when the app or the asset build is broken. Their CSS is inline, so when you change colors in main.css, update them there by hand.

Production

npm run build
NETTE_DEBUG=0 php nette migrate --force

Set the database, SMTP, mailFrom and appName in config/local.neon. Strict passwords follow production mode, so new passwords need 12 characters with mixed case, numbers and symbols.

Point the web server's document root to www/. Built assets go to www/assets/ (ignored by git, so build them on deploy).

Important: app/, config/, database/, log/ and temp/ must never be web-accessible. See the security warning.