rajtik76 / nette-tailwind-starter-kit
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
Requires
- php: >= 8.4
- latte/latte: ^3.1
- nette/application: ^3.2.3
- nette/assets: ^1.0.0
- nette/bootstrap: ^3.2.6
- nette/caching: ^3.2
- nette/database: ^3.2
- nette/di: ^3.2
- nette/forms: ^3.2
- nette/http: ^3.3
- nette/mail: ^4.0
- nette/robot-loader: ^4.0
- nette/security: ^3.2
- nette/utils: ^4.0
- psr/clock: ^1.0
- rajtik76/nette-console: ^0.2
- rajtik76/nette-migrations: ^0.2
- tracy/tracy: ^2.10
Requires (Dev)
- fakerphp/faker: ^1.24
- nette/phpstan-rules: ^1.0
- nette/tester: ^2.5
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
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.darkclass - Vite 8 with
@nette/vite-plugin, assets served bynette/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/mailwith Latte templates, written tolog/mailuntil 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 (
.nvmrcpins 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.
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.cssimports Tailwind and adds@source "../app". Vite's root isassets/, so without it Tailwind would not scan the Latte templates inapp/.assets/main.jsis the Vite entry point and importsmain.css.vite.config.tsregisters thetailwindcss()plugin next to the Nette plugin.app/Presentation/@layout.latterenders{asset? 'main.js'}, which outputs both the<script>and the<link rel="stylesheet">for the bundled CSS.- The
dark:variant follows the.darkclass on<html>. An inline script in@layout.lattesets it before the first paint fromlocalStorage(appearanceislight,darkor missing for system);assets/appearance.jschanges it at runtime and the Appearance settings page switches it through theappearanceAlpine component.
Use Tailwind classes directly in any .latte file under app/.
Welcome and error pages
- Colors and the font are defined in
@themeinassets/main.css. The font (Instrument Sans) is loaded from Bunny Fonts by the welcome page and the auth and app layouts, through theheadblock 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.lattewraps every 4xx template in a shared frame, so403.latte,404.latteetc. stay plain markup.500.phtmland503.phtmluse 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 inmain.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.