In this comprehensive tutorial, I’ll guide you through building an interactive dependent dropdown system in Laravel 12. We’ll implement a cascading selection menu for countries, states, and cities that dynamically updates based on user selections.
Understanding Dependent Dropdowns
A dependent dropdown is an intelligent form element where the options in one dropdown menu change based on the selection made in a previous dropdown. For instance:
- Selecting “United States” in the country dropdown would populate the state dropdown with options like “California” and “New York”
- Choosing “California” would then populate the city dropdown with options like “Los Angeles” and “San Francisco”
This creates a seamless, intuitive user experience by only showing relevant options at each level.
Implementation Overview
We’ll follow these steps to create our dynamic dropdown system:
- Set up a fresh Laravel 12 application
- Create database tables for countries, states, and cities
- Establish model relationships
- Implement controller logic
- Build the frontend with AJAX functionality
- Seed sample data
- Test our implementation
Step-by-Step Implementation
Step 1: Install Laravel 12
Begin by creating a new Laravel project:
composer create-project laravel/laravel dependent-dropdown-demo
cd dependent-dropdown-demo
Step 2: Create Database Migrations
Generate and configure migrations for our three tables:
php artisan make:migration create_countries_states_cities_tables
Update the migration file:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
Schema::create('states', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('country_id')->constrained();
$table->timestamps();
});
Schema::create('cities', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('state_id')->constrained();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('cities');
Schema::dropIfExists('states');
Schema::dropIfExists('countries');
}
};
Run the migrations:
php artisan migrate
Step 3: Create Eloquent Models
Generate models with their relationships:
php artisan make:model Country
php artisan make:model State
php artisan make:model City
Update the model files:
Country.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Country extends Model
{
use HasFactory;
protected $fillable = ['name'];
public function states(): HasMany
{
return $this->hasMany(State::class);
}
}
State.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class State extends Model
{
use HasFactory;
protected $fillable = ['name', 'country_id'];
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
public function cities(): HasMany
{
return $this->hasMany(City::class);
}
}
City.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class City extends Model
{
use HasFactory;
protected $fillable = ['name', 'state_id'];
public function state(): BelongsTo
{
return $this->belongsTo(State::class);
}
}
Step 4: Define Routes
Set up the necessary routes in routes/web.php:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\DropdownController;
Route::get('dropdown', [DropdownController::class, 'index']);
Route::post('api/fetch-states', [DropdownController::class, 'fetchState']);
Route::post('api/fetch-cities', [DropdownController::class, 'fetchCity']);
Step 5: Create the Controller
Generate and implement the controller logic:
php artisan make:controller DropdownController
Update the controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Country;
use App\Models\State;
use App\Models\City;
use Illuminate\View\View;
use Illuminate\Http\JsonResponse;
class DropdownController extends Controller
{
public function index(): View
{
return view('dropdown', [
'countries' => Country::all(['name', 'id'])
]);
}
public function fetchState(Request $request): JsonResponse
{
$states = State::where('country_id', $request->country_id)
->get(['name', 'id']);
return response()->json(['states' => $states]);
}
public function fetchCity(Request $request): JsonResponse
{
$cities = City::where('state_id', $request->state_id)
->get(['name', 'id']);
return response()->json(['cities' => $cities]);
}
}
Step 6: Create the View
Build the frontend interface in resources/views/dropdown.blade.php:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel 12 Dependent Dropdown Demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container py-5">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">Dynamic Dependent Dropdown Demo</h3>
</div>
<div class="card-body">
<form>
<div class="mb-3">
<label for="country-dropdown" class="form-label">Country</label>
<select id="country-dropdown" class="form-select">
<option value="">-- Select Country --</option>
@foreach ($countries as $country)
<option value="{{ $country->id }}">{{ $country->name }}</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label for="state-dropdown" class="form-label">State</label>
<select id="state-dropdown" class="form-select" disabled>
<option value="">-- Select State --</option>
</select>
</div>
<div class="mb-3">
<label for="city-dropdown" class="form-label">City</label>
<select id="city-dropdown" class="form-select" disabled>
<option value="">-- Select City --</option>
</select>
</div>
</form>
</div>
</div>
</div>
<script>
$(document).ready(function() {
// Country dropdown change event
$('#country-dropdown').change(function() {
const countryId = $(this).val();
$('#state-dropdown').html('<option value="">-- Select State --</option>');
$('#state-dropdown').prop('disabled', !countryId);
if (countryId) {
$.ajax({
url: "{{ url('api/fetch-states') }}",
type: "POST",
data: {
country_id: countryId,
_token: '{{ csrf_token() }}'
},
success: function(response) {
$.each(response.states, function(key, value) {
$('#state-dropdown').append(
`<option value="${value.id}">${value.name}</option>`
);
});
}
});
}
// Reset city dropdown
$('#city-dropdown').html('<option value="">-- Select City --</option>');
$('#city-dropdown').prop('disabled', true);
});
// State dropdown change event
$('#state-dropdown').change(function() {
const stateId = $(this).val();
$('#city-dropdown').html('<option value="">-- Select City --</option>');
$('#city-dropdown').prop('disabled', !stateId);
if (stateId) {
$.ajax({
url: "{{ url('api/fetch-cities') }}",
type: "POST",
data: {
state_id: stateId,
_token: '{{ csrf_token() }}'
},
success: function(response) {
$.each(response.cities, function(key, value) {
$('#city-dropdown').append(
`<option value="${value.id}">${value.name}</option>`
);
});
}
});
}
});
});
</script>
</body>
</html>
Step 7: Seed Sample Data
Create and run a seeder to populate our tables:
php artisan make:seeder CountryStateCitySeeder
Update the seeder:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Country;
use App\Models\State;
use App\Models\City;
class CountryStateCitySeeder extends Seeder
{
public function run(): void
{
// United States data
$us = Country::create(['name' => 'United States']);
$florida = State::create(['country_id' => $us->id, 'name' => 'Florida']);
City::create(['state_id' => $florida->id, 'name' => 'Miami']);
City::create(['state_id' => $florida->id, 'name' => 'Tampa']);
$california = State::create(['country_id' => $us->id, 'name' => 'California']);
City::create(['state_id' => $california->id, 'name' => 'Los Angeles']);
City::create(['state_id' => $california->id, 'name' => 'San Francisco']);
// India data
$india = Country::create(['name' => 'India']);
$gujarat = State::create(['country_id' => $india->id, 'name' => 'Gujarat']);
City::create(['state_id' => $gujarat->id, 'name' => 'Ahmedabad']);
City::create(['state_id' => $gujarat->id, 'name' => 'Surat']);
$maharashtra = State::create(['country_id' => $india->id, 'name' => 'Maharashtra']);
City::create(['state_id' => $maharashtra->id, 'name' => 'Mumbai']);
City::create(['state_id' => $maharashtra->id, 'name' => 'Pune']);
}
}
Run the seeder:
php artisan db:seed --class=CountryStateCitySeeder
Step 8: Launch the Application
Start the development server:
php artisan serve
Visit the application in your browser at http://localhost:8000/dropdown to see the dynamic dropdowns in action.
