Stefan Bauer
Developer and Creator of PingPing.io, a simple website monitoring.
Blog Building PingPing Newsletter About
"Building PingPing" was an open-source project. There have been several reasons why I discontinued creating it in public. PingPing.io has now a commercial version. If you have any questions about PingPing, its techniques, architecture or anything else, let me know. I might do a post about it. You can find the related sources for the old posts on GitHub.

#007: Designing a basic application layout

Published on March 27, 2018

A layout for our application views

An application layout

Currently we have a skeleton.blade.php layout file that we extend in all our application views. Since we will have different types of views in our app, it would be nice to have different templates to extend depending on the view we are creating. As our application views will have different sections compared to other views, we’ll create a aplication.blade.php file within our layouts folder.

--- /dev/null
+++ b/resources/views/layouts/application.blade.php
@@ -0,0 +1,27 @@
+@extends('layouts.skeleton')
+
+@section('pageTitle', 'PingPing')
+@section('bodyClasses', 'bg-grey-lighter text-base text-grey-darkest font-normal relative')
+
+@section('body')
+    <div class="min-h-screen flex flex-col">
+        @include('layouts.application.partials.navigation')
+
+        <main class="flex-1">
+            <div class="w-full max-w-screen mx-auto px-4 flex py-10">
+                @hasSection('sidebar')
+                <nav class="hidden lg:block lg:w-1/6 mr-6">
+                    @yield('sidebar')
+                </nav>
+                @endif
+
+                <div class="flex-1">
+                    @yield('content')
+                </div>
+            </div>
+        </main>
+
+        @include('layouts.application.partials.footer')
+    </div>
+    @
+@stop

Here we are extending our skeleton.blade.php file and including a few partials for the navigation, sidebar and footer. We’ll create those in a second.

We need to update our existing views

We already have a services.blade.php file that was extending our skeleton.blade.php layout, let’s update that now and we’ll also add some styling to that page.

--- a/resources/views/services/index.blade.php
+++ b/resources/views/services/index.blade.php
@@ -1,14 +1,26 @@
-<h1>Services</h1>
+@extends('layouts.application')

-<table>
-    <tr>
-        <td>Alias</td>
-        <td>URL</td>
-    </tr>
-    @foreach ($services as $service)
-        <tr>
-            <td>{{ $service->alias }}</td>
-            <td>{{ $service->url }}</td>
-        </tr>
-    @endforeach
-</table>
+@section('content')
+    <div class="bg-white rounded shadow border-t-4 border-primary">
+        <div class="flex justify-between items-center px-8 py-4 border-b">
+            <div class="text-lg text-black uppercase font-bold tracking-widest">
+                Services
+            </div>
+        </div>
+
+        <div class="px-8 py-8 bg-grey-lightest rounded-b">
+            <table>
+                <tr>
+                    <td>Alias</td>
+                    <td>URL</td>
+                </tr>
+                @foreach ($services as $service)
+                    <tr>
+                        <td>{{ $service->alias }}</td>
+                        <td>{{ $service->url }}</td>
+                    </tr>
+                @endforeach
+            </table>
+        </div>
+    </div>
+@stop

Let’s install a package

We will be using a package called thomaswelton/laravel-gravatar. Install it with Composer by running composer require "thomaswelton/laravel-gravatar". This package enables us to fetch an avatar for the logged in user. With this, we don't need to worry about allowing our users to upload an avatar to our application, we'll simply pull their avatar from gravatar.com.

Let’s create some partials

In the layout file we creating above, we included a few partials for the navigation, sidebar and footer sections of our app. Let’s create them now. Separating our views into smaller parts is an easy way to keep things organized.

--- /dev/null
+++ b/resources/views/layouts/application/partials/navigation.blade.php
@@ -0,0 +1,43 @@
+<nav class="w-full max-w-screen mx-auto px-4">
+    <div class="flex mt-6 py-3 px-2 border-b border-grey">
+        <div class="hidden lg:block lg:w-1/6 mr-6">
+            <a href="{{ route('services') }}">
+                @include('layouts.application.logo', ['class' => 'w-32'])
+            </a>
+        </div>
+        <div class="flex flex-auto justify-between items-center">
+            <ul class="list-reset flex pr-4">
+                <li>
+                    <a href="{{ route('services') }}" class="flex items-center no-underline text-grey-darker group hover:text-black mr-10">
+                        @include('assets.icons.services', ['class' => 'w-6 h-6 text-primary mr-2 group-hover:text-primary'])
+                        <span class="font-bold text-black">Services</span>
+                    </a>
+                </li>
+                <li>
+                    <a href="{{ route('settings.profile.edit') }}" class="flex items-center no-underline text-grey-darker group hover:text-black mr-10">
+                        @include('assets.icons.cog', ['class' => 'w-6 h-6 text-grey-dark mr-2 group-hover:text-primary'])
+                        <span>Settings</span>
+                    </a>
+                </li>
+                <li>
+                    <a href="{{ route('billing.credits') }}" class="flex items-center no-underline text-grey-darker group hover:text-black mr-10">
+                        @include('assets.icons.billing', ['class' => 'w-6 h-6 text-grey-dark mr-2 group-hover:text-primary'])
+                        <span>Billing</span>
+                    </a>
+                </li>
+            </ul>
+
+            <div class="flex items-center">
+                <span class="text-primary font-bold border-r border-grey pr-4 mr-4">100 Credits</span>
+                <ul class="list-reset flex pr-4">
+                    <li>
+                        <a href="{{ route('logout') }}" class="flex items-center no-underline text-grey-darker group hover:text-black">
+                            <img src="{{ Gravatar::src(auth()->user()->email, 200) }}" class="w-6 h-6 rounded-full mr-2"/>
+                            <span>Logout</span>
+                        </a>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+</nav>
--- /dev/null
+++ b/resources/views/settings/partials/sidebar.blade.php
@@ -0,0 +1,28 @@
+<ul class="list-reset mb-8">
+    <div class="pb-3 font-bold uppercase text-black">Settings</div>
+
+    <li>
+        <a href="#" class="flex items-center no-underline text-grey-darker group hover:text-black py-2">
+            @include('assets.icons.profile', ['class' => 'w-6 h-6 text-primary mr-3 group-hover:text-primary'])
+            <span class="font-bold text-black">My Profile</span>
+        </a>
+    </li>
+    <li>
+        <a href="#" class="flex items-center no-underline text-grey-darker group hover:text-black py-2">
+            @include('assets.icons.security', ['class' => 'w-6 h-6 text-grey-dark mr-3 group-hover:text-primary'])
+            <span class="">Security</span>
+        </a>
+    </li>
+    <li>
+        <a href="#" class="flex items-center no-underline text-grey-darker group hover:text-black py-2">
+            @include('assets.icons.notifications', ['class' => 'w-6 h-6 text-grey-dark mr-3 group-hover:text-primary'])
+            <span class="">Notifications</span>
+        </a>
+    </li>
+    <li>
+        <a href="#" class="flex items-center no-underline text-grey-darker group hover:text-black py-2">
+            @include('assets.icons.api', ['class' => 'w-6 h-6 text-grey-dark mr-3 group-hover:text-primary'])
+            <span class="">API Access</span>
+        </a>
+    </li>
+</ul>
--- /dev/null
+++ b/resources/views/layouts/application/partials/footer.blade.php
@@ -0,0 +1,12 @@
+<footer>
+    <div class="w-full max-w-screen mx-auto px-4 py-8 text-sm text-secondary">
+        <div class="flex justify-between">
+            <div>
+                &copy; 2017-{{ date('Y') }}. Created by <a href="https://twitter.com/stefanbauerme" target="_blank" rel="noopener noreferer" class="no-underline text-primary hover:text-
primary-dark">@stefanbauerme</a>
+            </div>
+            <div>
+                Some more links...
+            </div>
+        </div>
+    </div>
+</footer>

I’ve also created an icons folder within the partials folder. Inside there I have placed some files that contain some SVG icons we’ll just include where necessary using the @include blade directive. Take a look at the commit for episode #007: Designing a basic application layout to see all the icons we created.

More routes

We only have a handful of endpoints defined for our application. It’s time to add a few more. We need to create two routes, one for our credits resource and on for our profile page.

--- a/routes/web.php
+++ b/routes/web.php
@@ -23,5 +23,7 @@ Route::get('/logout', 'Auth\LoginController@logout')->name('logout');

 Route::middleware(['auth'])->group(function () {
     Route::get('/services', 'ServicesController@index')->name('services');
+    Route::get('/settings/profile', 'ProfileController@edit')->name('settings.profile.edit');
+    Route::get('/billing', 'CreditsController@index')->name('billing.credits');
 });

We created our routes within the group that has the auth middleware applied to it, anyone who visits those routes must be logged in our application. We’ll create the controllers in a bit.

We need some controllers

We need to create the controllers that we defined in our routes file. We will create two controllers called CreditsController and ProfileController. The CreditsController will be used to handle everything related with our credits resource. In the other hand, the ProfileController will be used for everything related with the users profile. At the moment we are only returning a view from each controller.

--- /dev/null
+++ b/app/Http/Controllers/CreditsController.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Http\Controllers;
+
+class CreditsController extends Controller
+{
+    public function index()
+    {
+        return view('billing.credits.index');
+    }
+}
--- /dev/null
+++ b/app/Http/Controllers/ProfileController.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Http\Controllers;
+
+class ProfileController extends Controller
+{
+    public function edit()
+    {
+        return view('settings.profile.edit');
+    }
+}

Here we created our 2 controllers, they each have one method that simply renders a corresponding view.

Let’s create the views.

We previously created two controllers that each render a view. We are going to create those views now. The views will be very simple for now, but we'll keep adding new things as we move forward.

--- /dev/null
+++ b/resources/views/billing/credits/index.blade.php
@@ -0,0 +1,15 @@
+@extends('layouts.application')
+
+@section('content')
+    <div class="bg-white rounded shadow border-t-4 border-primary">
+        <div class="flex justify-between items-center px-8 py-4 border-b">
+            <div class="text-lg text-black uppercase font-bold tracking-widest">
+                Credits
+            </div>
+        </div>
+
+        <div class="px-8 py-8 bg-grey-lightest rounded-b">
+            ...
+        </div>
+    </div>
+@stop
--- /dev/null
+++ b/resources/views/settings/profile/edit.blade.php
@@ -0,0 +1,19 @@
+@extends('layouts.application')
+
+@section('sidebar')
+    @include ('settings.partials.sidebar')
+@stop
+
+@section('content')
+    <div class="bg-white rounded shadow border-t-4 border-primary">
+        <div class="flex justify-between items-center px-8 py-4 border-b">
+            <div class="text-lg text-black uppercase font-bold tracking-widest">
+                My Profile
+            </div>
+        </div>
+
+        <div class="px-8 py-8 bg-grey-lightest rounded-b">
+            ...
+        </div>
+    </div>
+@stop

Tailwind Configuration

We’ve been working mostly with the default configuration that Tailwind provides, but It would be nice to have a similar color palette like bootstrap and be able to use classes like success, danger, primary, etc. When using Tailwind, adding colors to use with utilities is very simple.

--- a/tailwind.js
+++ b/tailwind.js
@@ -56,10 +56,29 @@ let colors = {
   'grey-lightest': '#fcfcfc',
   'white': '#ffffff',

-  'primary': '#2b79c1',
-  'primary-dark': '#266299',
+  'primary-light': '#93c7fe',
+  'primary': '#4598db',
+  'primary-dark': '#006cb8',

-  'red': '#bf6464',
+  'secondary-light': '#d2d5da',
+  'secondary': '#96a5b8',
+  'secondary-dark': '#5a7897',
+
+  'info-light': '#a9f8ff',
+  'info': '#4cc8f9',
+  'info-dark': '#009acc',
+
+  'warning-light': '#fffa9a',
+  'warning': '#ecc657',
+  'warning-dark': '#b99600',
+
+  'success-light': '#a2ffa9',
+  'success': '#5ed66e',
+  'success-dark': '#00a633',
+
+  'danger-light': '#ff8795',
+  'danger': '#ed4763',
+  'danger-dark': '#bf0036'
 }

 module.exports = {
@@ -294,6 +313,24 @@ module.exports = {

   backgroundColors: colors,

+  /*
+  |-----------------------------------------------------------------------------
+  | Background sizes               https://tailwindcss.com/docs/background-size
+  |-----------------------------------------------------------------------------
+  |
+  | Here is where you define your background sizes. We provide some common
+  | values that are useful in most projects, but feel free to add other sizes
+  | that are specific to your project here as well.
+  |
+  | Class name: .bg-{size}
+  |
+  */
+
+  backgroundSize: {
+      'auto': 'auto',
+      'cover': 'cover',
+      'contain': 'contain',
+  },

   /*
   |-----------------------------------------------------------------------------
@@ -517,6 +554,7 @@ module.exports = {
     '4xl': '90rem',
     '5xl': '100rem',
     'full': '100%',
+    'screen': '1400px',
   },

@@ -593,6 +631,7 @@ module.exports = {
     '4': '1rem',
     '6': '1.5rem',
     '8': '2rem',
+    '10': '2.5rem',
   },

@@ -740,7 +779,12 @@ module.exports = {
   | Here is where you control which modules are generated and what variants are
   | generated for each of those modules.
   |
   |
   | To disable a module completely, use `false` instead of an array.
   |
@@ -749,7 +793,7 @@ module.exports = {
   modules: {
     appearance: ['responsive'],
     backgroundAttachment: ['responsive'],
-    backgroundColors: ['responsive', 'hover'],
+    backgroundColors: ['responsive', 'hover', 'group-hover'],
     backgroundPosition: ['responsive'],
     backgroundRepeat: ['responsive'],
     backgroundSize: ['responsive'],
@@ -782,7 +826,7 @@ module.exports = {
     svgFill: [],
     svgStroke: [],
     textAlign: ['responsive'],
-    textColors: ['responsive', 'hover'],
+    textColors: ['responsive', 'hover', 'group-hover'],
     textSizes: ['responsive'],
     textStyle: ['responsive', 'hover'],
     tracking: ['responsive'],
@@ -794,6 +838,26 @@ module.exports = {
     zIndex: ['responsive'],
   },

+  /*
+  |-----------------------------------------------------------------------------
+  | Plugins                                https://tailwindcss.com/docs/plugins
+  |-----------------------------------------------------------------------------
+  |
+  | Here is where you can register any plugins you'd like to use in your
+  | project. Tailwind's built-in `container` plugin is enabled by default to
+  | give you a Bootstrap-style responsive container component out of the box.
+  |
+  | Be sure to view the complete plugin documentation to learn more about how
+  | the plugin system works.
+  |
+  */
+
+  plugins: [
+    // require('tailwindcss/plugins/container')({
+    //     center: true,
+    //     padding: '1rem',
+    // }),
+  ],

   /*
   |-----------------------------------------------------------------------------

We’ve updated the configuration file and added a few new things. The new colors we added will be used by Tailwind to generate all the utility classes for each one. We also added some new maxWidth and margin properties.

An important change we did, was adding a new state variant for our backgroundColors and textColors utility classes. By default, Tailwind only generates classes for the responsive and hover state variants, but we added the group-hover state.

In a previous commit we updated our npm packages, bumping up our Tailwind dependency to 0.5. This new release allowed us to customize our background size utility classes, use the new plugin system, added support to the active state and many more interesting things. You can take a look at the release notes here.

Let’s use our new colors

We just created our new colors, let’s update our views to use them. We’ll start by updating our login.blade.php file.

--- a/resources/views/auth/login.blade.php
+++ b/resources/views/auth/login.blade.php
@@ -5,14 +5,14 @@

 @section('body')
     <div class="h-2 bg-primary"></div>
-    <div class="container mx-auto p-8">
+    <div class="container p-8">
         <div class="mx-auto max-w-sm">
             <div class="py-10 text-center">
                 @include('logo', ['style' => 'max-width: 12rem;'])
             </div>

             <div class="bg-white rounded shadow">
-                <div class="border-b py-8 font-bold text-black text-center text-xl tracking-widest uppercase">
+                <div class="border-b border-grey-lighter py-8 font-bold text-black text-center text-xl tracking-widest uppercase">
                     Welcome back!
                 </div>

@@ -20,25 +20,25 @@
                     {{ csrf_field() }}

                     <div class="mb-3">
-                        <input class="border {{ $errors->first('email') ? 'border-red' : '' }} w-full p-3" name="email" type="text" placeholder="E-Mail">
+                        <input class="rounded border border-grey-lighter {{ $errors->first('email') ? 'border-danger' : '' }} w-full p-3" name="email" type="text" placeholder="E-Mail">
                         @if ($errors->first('email'))
-                            <p class="text-red text-sm mt-1">{{ $errors->first('email') }}</p>
+                            <p class="text-danger text-sm mt-1">{{ $errors->first('email') }}</p>
                         @endif
                     </div>
                     <div class="mb-6">
-                        <input class="border {{ $errors->first('password') ? 'border-red' : '' }} w-full p-3" name="password" type="password" placeholder="**************">
+                        <input class="rounded border border-grey-lighter {{ $errors->first('password') ? 'border-danger' : '' }} w-full p-3" name="password" type="password" placeholder="**************">
                         @if ($errors->first('password'))
-                            <p class="text-red text-sm mt-1">{{ $errors->first('password') }}</p>
+                            <p class="text-danger text-sm mt-1">{{ $errors->first('password') }}</p>
                         @endif
                     </div>
                     <div class="flex">
-                        <button type="submit" class="bg-primary hover:bg-primary-dark w-full p-4 text-sm text-white uppercase font-bold tracking-wider">
+                        <button type="submit" class="rounded bg-primary hover:bg-primary-dark w-full p-4 text-sm text-white uppercase font-bold tracking-wider">
                             Login
                         </button>
                     </div>
                 </form>

-                <div class="border-t px-10 py-6">
+                <div class="border-t border-grey-lighter px-10 py-6">
                     <div class="flex justify-between">
                         <a href="#" class="font-bold text-primary hover:text-primary-dark no-underline">Don't have an account?</a>
                         <a href="#" class="text-grey-darkest hover:text-black no-underline">Forgot Password?</a>

Here we simply changed our classes to use the new utilities we configured for Tailwind.

We did a lot on this episode and I’ve only highlighted the most important parts here. Feel free to take a look at the commit for this episode to see every change in more detail. You can find the commit here.

If you enjoy my posts or you like what I'am talking/twittering about, you should sign up. I will show you everything I know about development and UI/UX.

Imprint Cookie Policy Privacy Policy
Proudly hosted with Vultr