Laravel, Livewire, and Tailwind CSS: Go-To Conventions for Efficiency

Published January 2nd, 2023
8 minute read

Although the Laravel framework has its own conventions, there are several others I tend to follow that (I think) make me more efficient, so I want to share them with the world. These are my personal “best practices” for conventions in Laravel models, Laravel Livewire components, and Tailwind CSS utility classes.

Eloquent Model Method Hierarchy

For Model classes, I like to use a specific hierarchy when ordering the methods inside of them. I order methods in Eloquent Models like this:

Model Event Hooks => Relations => Accessors & Mutators => Everything Else

By following a consistent hierarchy in the organization of your model methods, it is easier for other developers to understand and maintain your code. Placing model events / hooks first helps draw attention to important code that runs at specific points in the model's lifecycle. Similarly, placing relations before accessors and mutators can help make it clear which methods are responsible for interacting with other models, and which are responsible for modifying the current model's data.

To summarize, following these order guidelines for models has two big benefits:

  • It mimics the model lifecycle and data flow, making it easier to grok and think about
  • It makes navigating the code faster because it’s ordered in a consistent, predictable way.

Here’s an example model:

1class Podcast extends Model
2{
3 // Model event hooks and global scopes first
4 protected static function booted()
5 {
6 static::creating(fn ($podcast) => $podcast->user_id = auth()->id());
7 
8 static::addGlobalScope('byUser', function (Builder $builder) {
9 $builder->where('user_id', auth()->id());
10 });
11 }
12 
13 // Relations next
14 public function episodes()
15 {
16 return $this->hasMany(Episode::class);
17 }
18 
19 // Accessors and Mutators
20 protected function isPublished(): Attribute
21 {
22 return Attribute::make(
23 get: fn () => $this->published_at !== null,
24 );
25 }
26 
27 // ... Everything else
28}
1class Podcast extends Model
2{
3 // Model event hooks and global scopes first
4 protected static function booted()
5 {
6 static::creating(fn ($podcast) => $podcast->user_id = auth()->id());
7 
8 static::addGlobalScope('byUser', function (Builder $builder) {
9 $builder->where('user_id', auth()->id());
10 });
11 }
12 
13 // Relations next
14 public function episodes()
15 {
16 return $this->hasMany(Episode::class);
17 }
18 
19 // Accessors and Mutators
20 protected function isPublished(): Attribute
21 {
22 return Attribute::make(
23 get: fn () => $this->published_at !== null,
24 );
25 }
26 
27 // ... Everything else
28}

Livewire Component Guidelines

With Livewire components, I follow similar guidelines. It’s helpful to model the flow of the component, so a couple of things I usually do are:

Start with the mount() method

Since mount() is Livewire’s equivalent of a constructor, I like to keep it at the top (if your component has one, that is). This is the method that gets called when a component is instantiated, so it makes sense to keep it at the beginning for both visibility and ease of mentally modeling of the component lifecycle.

Computed properties after mount()

Mirroring how I think about Eloquent models, I find that it’s useful to keep the computed properties close to the top, after mount(), but before any handler methods. Since these are accessed similarly to public properties, it makes more sense to me to have them grouped, rather than spread around the component.

Put the render() method last

Similar to the mount method being first, the render() method tends to be the last step in the component lifecycle, so I always keep it at the bottom. Think of it this way. The component is mounted, Livewire does the in-between stuff, then it renders the view. This (like most of these conventions) has the added benefit of allowing me to easily and predictably find the render method in big components.

Here’s an example Livewire component:

1<?php
2 
3namespace App\Http\Livewire;
4 
5use App\Models\Client;
6use Livewire\Component;
7 
8class ClientCreate extends Component
9{
10 public Client $client;
11 
12 protected $rules = [
13 'client.name' => 'required',
14 ];
15 
16 // Mount first, after properties
17 public function mount()
18 {
19 $this->client = Client::make();
20 }
21 
22 // Computed properties next
23 public function getNicknameProperty()
24 {
25 return "The great".$this->client->name;
26 }
27 
28 // Methods and anything else
29 public function create()
30 {
31 // ...
32 }
33 
34 // Always render() at the bottom
35 public function render()
36 {
37 return view('livewire.client-create');
38 }
39}
1<?php
2 
3namespace App\Http\Livewire;
4 
5use App\Models\Client;
6use Livewire\Component;
7 
8class ClientCreate extends Component
9{
10 public Client $client;
11 
12 protected $rules = [
13 'client.name' => 'required',
14 ];
15 
16 // Mount first, after properties
17 public function mount()
18 {
19 $this->client = Client::make();
20 }
21 
22 // Computed properties next
23 public function getNicknameProperty()
24 {
25 return "The great".$this->client->name;
26 }
27 
28 // Methods and anything else
29 public function create()
30 {
31 // ...
32 }
33 
34 // Always render() at the bottom
35 public function render()
36 {
37 return view('livewire.client-create');
38 }
39}

Tailwind CSS and Utility Class Order

Lastly, there’s a couple of Tailwind CSS conventions I try to follow. This one is definitely more loose and it’s only because there’s no official integration for Laravel Blade with the Tailwind CSS Prettier plugin. Here’s how I organize utility classes:

  1. Classes that affect spacing, layout, and position are usually first

  2. Next, “general” classes. Ones affect the design, color, visuals (most utility classes)

  3. Modifier classes (such as focus: or hover: next). These are often duplicates or extensions of the general classes that come with conditions, so having them follow the general base classes makes the most sense to me.

  4. Dark mode classes are last in the overall class list, but always the first modifier if combined with other modifiers. For example, consider the following combination:

    1<div class="hover:text-blue-500 dark:hover:text-blue-200"></div>
    1<div class="hover:text-blue-500 dark:hover:text-blue-200"></div>

    In this case I find that thinking of it as “if we’re in darkmode and hovering” is more straightforward than “if we’re hovering and oh, it’s darkmode”. To me, dark: seems more like a blanket modifier, so I always make it the most prominent in the individual utility modifier stack. This convention also tends to follow the examples from Tailwind CSS.

Do you follow any unspoken conventions in your applications? Perhaps you disagree with the ones I use? Either way, I’d love to hear your thoughts on this topic. DM me, let’s talk!

Enjoy this article? Follow me on Twitter for more tips, articles and links.
😢 Awww, nobody has liked or mentioned this on Twitter yet.

Join the Newsletter ❤️

A most excellent monthly newsletter with code & design tips, curated links and more!
I'll never spam you. Unsubscribe at any time.
Copyright ©2023 Austen Cameron