m1ge0/bootsjet-starter-kit
A Laravel starter kit that pairs Jetstream with a Bootstrap frontend, giving you authentication, teams, and profile scaffolding styled with Bootstrap instead of Tailwind. A quick starting point for Bootstrap-based Laravel apps.
Bootsjet Starter Kit is a project template, not a Composer dependency — you consume it once at scaffold time and then own the code. It gives you Laravel 12 + Jetstream (Livewire + Teams) pre-wired to Bootstrap 5 and Sass instead of the default Tailwind stack.
Prerequisites
--using must be supported):composer global update laravel/installer
Scaffold
laravel new my-project --using=m1ge0/bootsjet-starter-kit
# or, if the shorthand fails (auth / rate limits / forks):
laravel new my-project --using="https://github.com/m1ge0/bootsjet-starter-kit"
First run
cd my-project
cp .env.example .env # if the installer didn't already
php artisan key:generate
npm install
npm run build
php artisan migrate
php artisan serve
Register a user, create a team, and confirm the dashboard renders with Bootstrap styles before writing any code — that validates Sass compilation, asset wiring, and the Teams migrations in one pass.
Where to look first
| Path | Why |
|---|---|
resources/views/ |
Jetstream/Teams views are already converted to Bootstrap and committed as first-class files — this is where you customise, not the vendor package |
resources/sass/app.scss |
Single styling entry point; Bootstrap is imported here |
vite.config.js |
Confirms the resources/sass/app.scss + resources/js/app.js inputs |
app/Actions/Jetstream/ |
Team lifecycle (CreateTeam, AddTeamMember, UpdateTeamMemberRole, DeleteTeam) — your main domain extension point |
app/Providers/JetstreamServiceProvider.php |
Roles, permissions, password rules, profile updates |
config/jetstream.php |
features.teams, profile photos, API tokens |
app/Models/Team.php, app/Policies/TeamPolicy.php |
Authorization surface for team-scoped work |
First real use case: replace the dashboard card content with a team-scoped Livewire component (php artisan make:livewire TeamNotes) rendered with Bootstrap cards and forms — this exercises routing, auth, team context, Livewire, and your Sass pipeline simultaneously.
Override Bootstrap defaults before the import, in resources/sass/app.scss:
$primary: #0b5ed7;
$border-radius: .5rem;
$font-family-sans-serif: 'Inter', system-ui, sans-serif;
@import 'bootstrap/scss/bootstrap';
Keep project-specific partials in resources/sass/ and import them after Bootstrap so they can win on specificity.
Bootstrap's JS widgets are imperative; Livewire is declarative. Bridge them in resources/js/app.js:
import * as bootstrap from 'bootstrap';
window.bootstrap = bootstrap;
document.addEventListener('livewire:navigated', () => {
// re-init anything that survived a wire:navigate swap
document.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach(el => new bootstrap.Tooltip(el));
});
For modals, drive them from the server with events rather than toggling classes by hand:
// In the Livewire component
$this->dispatch('open-modal', id: 'edit-team');
<div wire:on="open-modal" x-data
x-init="bootstrap.Modal.getOrCreateInstance($refs.modal).show()"
x-ref="modal" class="modal fade">...</div>
Prefer Livewire's own validation + wire:model for forms and Bootstrap only for presentation (is-invalid, invalid-feedback) so state stays server-authoritative.
Always resolve from the authenticated user's current team rather than trusting route parameters:
$team = auth()->user()->currentTeam;
abort_unless(auth()->user()->belongsToTeam($team), 403);
$notes = $team->notes()->latest()->paginate(15);
Add a team_id column plus a relationship on the parent model, and put authorization in TeamPolicy (view, update, addTeamMember, …). For stricter isolation, attach a global scope in a BelongsToTeam trait that filters on Jetstream::hasTeamFeatures() ? auth()->user()->currentTeam?->id : null, with an explicit withoutTeamScope() escape hatch for admin tooling.
Configure in app/Providers/JetstreamServiceProvider.php:
Jetstream::role('admin', 'Administrator', [
'note:read', 'note:create', 'note:update', 'note:delete',
])->description('Full access to the team workspace.');
Then gate in components: Gate::allows('create', [Note::class, $team]) or $user->hasTeamPermission($team, 'note:update'). Custom abilities registered via Jetstream::permissions([...]) show up automatically in the team-member UI.
The repo's workflow runs composer validate --strict, npm run build, and php artisan test. Mirror that locally before pushing, and keep at least one feature test per team action:
public function test_member_cannot_delete_another_teams_note(): void
{
$user = User::factory()->withPersonalTeam()->create();
$note = Note::factory()->create(); // different team
$this->actingAs($user)->get(route('notes.show', $note))->assertForbidden();
}
composer.json, so upstream fixes never arrive via composer update. Add the repo as a git remote upstream and cherry-pick/diff, or re-scaffold when starting fresh projects. Decide this on day one; retrofitting is painful.php artisan jetstream:install livewire --teams will overwrite the Bootstrap views with Tailwind ones and reintroduce Tailwind deps. Customise resources/views/ in place instead of publishing over it.--using needs a current installer. Older global installers ignore or reject the flag. If the shorthand repo name fails, switch to the full HTTPS URL — it's also the path for private forks.sass or sass-embedded) present in devDependencies; if npm run build dies on app.scss, that's the first thing to check. Bootstrap 5.3 still ships @import-based sources, so modern Dart Sass prints deprecation warnings — cosmetic today, but pin sass if the noise blocks CI, and plan a migration when Bootstrap moves to @use.bootstrap (and @popperjs/core) imported explicitly, and any component injected by Livewire after wire:navigate needs re-initialisation on livewire:navigated. Symptom: first click opens a dropdown, second click does nothing.^3 || ^4 is permissive by design. Adding a package that requires Livewire 4 can silently change directive behaviour. Pin an explicit constraint in composer.json once the app is in production, and re-run the full test suite after any Livewire bump.composer validate --strict is a CI gate. Adding a dependency without updating composer.lock (or leaving a stale name/description) fails the pipeline before tests even run.currentTeam — pass the team ID explicitly into jobs and use withoutTeamScope() inside them.npm run dev and npm run build write to the same public/build; after switching modes, hard-refresh. Laravel 12's @vite directive throws loudly on a missing manifest, which is a clearer failure than silently unstyled pages.php artisan about (env/features), php artisan route:list --path=teams (Jetstream's package routes are not in routes/web.php), and config('jetstream.features') when Teams UI unexpectedly disappears.How can I help you explore Laravel packages today?