One place for hosting & domains

      Understanding and Working with Files in Laravel

      Introduction

      File uploads are one the most commonly used features on the web. From uploading avatars to family pictures to sending documents via email, we can’t do without files on the web.

      In today’s article will cover all the ways to handle files in Laravel. After reading the article, If we left something out please let us know in the comments and we’ll update the post accordingly.

      Handling files is another thing Laravel has simplified in its ecosystem. Before we get started, we’ll need a few things. First, a Laravel project. There are a few ways to create a new Laravel project, but let’s stick to composer for now.

      1. composer create-project --prefer-dist laravel/laravel files

      Where files is the name of our project. After installing the app, we’ll need a few packages installed, so, let’s get them out of the way. You should note that these packages are only necessary if you intend to save images to Amazon’s s3 or manipulate images like cropping, filters, etc.

      1. composer require league/flysystem-aws-s3-v3:~1.0 intervention/image:~2.4

      After installing the dependencies, the final one is Mailtrap. Mailtrap is a fake SMTP server for development teams to test, view, and share emails sent from the development and staging environments without spamming real customers. So head over to Mailtrap and create a new inbox for testing.

      Then, in welcome.blade.php update the head tag to:

      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>File uploads</title>
      <style>
        * {
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
              "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
              "Segoe UI Emoji", "Segoe UI Symbol";
        }
      </style>
      

      Modify the body contents to:

      <form action="/process" enctype="multipart/form-data" method="POST">
          <p>
              <label for="photo">
                  <input type="file" name="photo" id="photo">
              </label>
          </p>
          <button>Upload</button>
          {{ csrf_field() }}
      </form>
      

      For the file upload form, the enctype="multipart/form-data" and method="POST" are extremely important as the browser will know how to properly format the request. {{ csrf_field() }} is Laravel specific and will generate a hidden input field with a token that Laravel can use to verify the form submission is legit.

      If the CSRF token does not exist on the page, Laravel will show “The page has expired due to inactivity” page.

      Now that we have our dependencies out of the way, let’s get started.

      Development, as we know it in 2018, is growing fast, and in most cases, there are many solutions to one problem. Take file hosting, for example, now we have so many options to store files, the sheer number of solutions ranging from self-hosted to FTP to cloud storage to GFS and many others.

      Since Laravel is framework that encourages flexibility, it has a native way to handle the many file structures. Be it local, Amazon’s s3, Google’s Cloud, Laravel has you covered.

      Laravel’s solution to this problem is to call them disks. Makes sense, any file storage system you can think of can be labeled as a disk in Laravel. In this regard, Laravel comes with native support for some providers (disks). We have local, public, s3, Rackspace, FTP, etc. All this is possible because of Flysystem.

      If you open config/filesystems.php you’ll see the available disks and their respected configuration.

      From the introduction section above, we have a form with a file input ready to be processed. We can see that the form is pointed to /process. In routes/web.php, we define a new POST /process route.

      use Illuminate\Http\Request;
      
      Route::post('process', function (Request $request) {
          $path = $request->file('photo')->store('photos');
      
          dd($path);
      });
      

      What the above code does is grab the photo field from the request and save it to the photos folder. dd() is a Laravel function that kills the running script and dumps the argument to the page. For me, the file was saved to photos/3hcX8yrOs2NYhpadt4Eacq4TFtpVYUCw6VTRJhfn.png. To find this file on the file system, navigate to storage/app and you’ll find the uploaded file.

      If you don’t like the default naming pattern provided by Laravel, you can provide yours using the storeAs method.

      Route::post('process', function (Request $request) {
          
          $file = $request->file('photo');
          
          
          $filename = 'profile-photo-' . time() . '.' . $file->getClientOriginalExtension();
          
          
          $path = $file->storeAs('photos', $filename);
          
          dd($path);
      });
      

      After running the above code, I got photos/profile-photo-1517311378.png.

      In config/filesystems.php you can see the disks local and public defined. By default, Laravel uses the local disk configuration. The major difference between local and the public disk is that local is private and cannot be accessed from the browser while public can be accessed from the browser.

      Since the public disk is in storage/app/public and Laravel’s server root is in public you need to link storage/app/public to Laravel’s public folder. We can do that with our trusty artisan by running php artisan storage:link.

      Since Laravel doesn’t provide a function to upload multiple files, we need to do that ourselves. It’s not much different from what we’ve been doing so far, we just need a loop.

      First, let’s update our file upload input to accept multiple files.

      <input type="file" name="photos[]" id="photo" multiple>
      

      When we try to process this $request->file(‘photos’), it’s now an array of UploadedFile instances so we need to loop through the array and save each file.

      Route::post('process', function (Request $request) {
          $photos = $request->file('photos');
          $paths  = [];
      
          foreach ($photos as $photo) {
              $extension = $photo->getClientOriginalExtension();
              $filename  = 'profile-photo-' . time() . '.' . $extension;
              $paths[]   = $photo->storeAs('photos', $filename);
          }
      
          dd($paths);
      });
      

      After running this, I got the following array, since I uploaded a GIF and a PNG:

      array:2 [0 => "photos/profile-photo-1517315875.gif"
        1 => "photos/profile-photo-1517315875.png"
      ]
      

      Validation for file uploads is extremely important. Apart from preventing users from uploading the wrong file types, it’s also for security. Let me give an example regarding security. There’s a PHP configuration option cgi.fix_pathinfo=1. What this does is when it encounters a file like https://example.com/images/evil.jpg/nonexistent.php, PHP will assume nonexistent.php is a PHP file and it will try to run it. When it discovers that nonexistent.php doesn’t exist, PHP will be like “I need to fix this ASAP” and try to execute evil.jpg (a PHP file disguised as a JPEG). Because evil.jpg wasn’t validated when it was uploaded, a hacker now has a script they can freely run live on your server… Not… good.

      To validate files in Laravel, there are so many ways, but let’s stick to controller validation.

      Route::post('process', function (Request $request) {
          
          $validation = $request->validate([
              'photo' => 'required|file|image|mimes:jpeg,png,gif,webp|max:2048'
              
              
          ]);
          $file      = $validation['photo']; 
          $extension = $file->getClientOriginalExtension();
          $filename  = 'profile-photo-' . time() . '.' . $extension;
          $path      = $file->storeAs('photos', $filename);
      
          dd($path);
      });
      

      For the above snippet, we told Laravel to make sure the field with the name of the photo is required, a successfully uploaded file, it’s an image, it has one of the defined mime types, and it’s a max of 2048 kilobytes ~~ 2 megabytes.

      Now, when a malicious user uploads a disguised file, the file will fail validation and if for some weird reason you leave cgi.fix_pathinfo on, this is not a means by which you can get PWNED!!!

      If you head over to Laravel’s validation page you’ll see a whole bunch of validation rules.

      Okay, your site is now an adult, it has many visitors and you decide it’s time to move to the cloud. Or maybe from the beginning, you decided your files will live on a separate server. The good news is Laravel comes with support for many cloud providers, but, for this tutorial, let’s stick with Amazon.

      Earlier we installed league/flysystem-aws-s3-v3 through composer. Laravel will automatically look for it if you choose to use Amazon S3 or throw an exception.

      To upload files to the cloud, just use:

      $request->file('photo')->store('photos', 's3');
      

      For multiple file uploads:

      foreach ($photos as $photo) {
          $extension = $photo->getClientOriginalExtension();
          $filename  = 'profile-photo-' . time() . '.' . $extension;
          $paths[]   = $photo->storeAs('photos', $filename, 's3');
      }
      

      Users may have already uploaded files before you decide to switch to a cloud provider, you can check the upcoming sections for what to do when files already exist.

      Note: You’ll have to configure your Amazon S3 credentials in config/filesystems.php.

      Before we do this, let’s quickly configure our mail environment. In .env file you will see this section

      MAIL_DRIVER=smtp
      MAIL_HOST=smtp.mailtrap.io
      MAIL_PORT=2525
      MAIL_USERNAME=null
      MAIL_PASSWORD=null
      MAIL_ENCRYPTION=null
      

      We need a username and password which we can get at Mailtrap.io. Mailtrap is really good for testing emails during development as you don’t have to crowd your email with spam. You can also share inboxes with team members or create separate inboxes.

      First, create an account and login:

      1. Create a new inbox
      2. Click to open inbox
      3. Copy username and password under SMTP section

      After copying credentials, we can modify .env to:

      MAIL_DRIVER=smtp
      MAIL_HOST=smtp.mailtrap.io
      MAIL_PORT=2525
      MAIL_USERNAME=USERNAME
      MAIL_PASSWORD=PASSWORD
      MAIL_ENCRYPTION=null
      

      Don’t bother using mine, I deleted it.

      Create your mailable

      1. php artisan make:mail FileDownloaded

      Then, edit its build method and change it to:

      public function build()
      {
          return $this->from('[email protected]')
              ->view('emails.files_downloaded')
              ->attach(storage_path('app/file.txt'), [
                  'as' => 'secret.txt'
              ]);
      }
      

      As you can see from the method above, we pass the absolute file path to the attach() method and pass an optional array where we can change the name of the attachment or even add custom headers. Next, we need to create our email view.

      Create a new view file in resources/views/emails/files_downloaded.blade.php and place the content below.

      <h1>Only you can stop forest fires</h1>
      <p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Labore at reiciendis consequatur, ea culpa molestiae ad minima est quibusdam ducimus laboriosam dolorem, quasi sequi! Atque dolore ullam nisi accusantium. Tenetur!</p>
      

      Now, in routes/web.php we can create a new route and trigger a mail when we visit it.

      use App\Mail\FileDownloaded;
      
      Route::get('mail', function () {
          $email = '[email protected]';
      
          Mail::to($email)->send(new FileDownloaded);
      
          dd('done');
      });
      

      If you head over to Mailtrap, you should see this.

      In an application, it’s not every time we process files through uploads. Sometimes, we decide to defer cloud file uploads till a certain user action is complete. Other times we have some files on disk before switching to a cloud provider. For times like this, Laravel provides a convenient Storage facade. For those who don’t know, facades in Laravel are class aliases. So instead of doing something like Symfony\File\Whatever\Long\Namespace\UploadedFile, we can do Storage instead.

      Choosing a disk to upload a file. If no disk is specified, Laravel looks in config/filesystems.php and uses the default disk.

      Storage::disk('local')->exists('file.txt');
      

      Use default cloud provider:

      
      Storage::cloud()->exists('file.txt');
      

      Create a new file with contents:

      Storage::put('file.txt', 'Contents');
      

      Prepend to file:

      Storage::prepend('file.txt', 'Prepended Text');
      

      Append to file:

      Storage::append('file.txt', 'Prepended Text');
      

      Get file contents:

      Storage::get('file.txt')
      

      Check if file exists:

      Storage::exists('file.txt')
      

      Force file download:

      Storage::download('file.txt', $name, $headers); 
      

      Generate publicly accessible URL:

      Storage::url('file.txt');
      

      Generate a temporary public URL (i.e., files that won’t exist after a set time). This will only work for cloud providers as Laravel doesn’t yet know how to handle the generation of temporary URLs for the local disk.

      Storage::temporaryUrl('file.txt’, now()->addMinutes(10));
      

      Get file size:

      Storage::size('file.txt');
      

      Last modified date:

      Storage::lastModified('file.txt')
      

      Copy files:

      Storage::copy('file.txt', 'shared/file.txt');
      

      Move files:

      Storage::move('file.txt', 'secret/file.txt');
      

      Delete files:

      Storage::delete('file.txt');
      

      To delete multiple files:

      Storage::delete(['file1.txt', 'file2.txt']);
      

      Resizing images, adding filters, etc. This is where Laravel needs external help. Adding this feature natively to Laravel will only bloat the application since no installs need it. We need a package called intervention/image. We already installed this package, but for reference.

      1. composer require intervention/image

      Since Laravel can automatically detect packages, we don’t need to register anything. If you are using a version of Laravel lesser than 5.5 read this.

      To resize an image

      $image = Image::make(storage_path('app/public/profile.jpg'))->resize(300, 200);
      

      Even Laravel’s packages are fluent.

      You can head over to their website and see all the fancy effects and filters you can add to your image.

      Laravel also provides handy helpers to work with directories. They are all based on PHP iterators so they’ll provide the utmost performance.

      To get all files:

      Storage::files
      

      To get all files in a directory including files in sub-folders

      Storage::allFiles($directory_name);
      

      To get all directories within a directory

      Storage::directories($directory_name);
      

      To get all directories within a directory including files in sub-directories

      Storage::allDirectories($directory_name);
      

      Make a directory

      Storage::makeDirectory($directory_name);
      

      Delete a directory

      Storage::deleteDirectory($directory_name);
      

      If we left anything out, please let us know down in the comments. Also, checkout Mailtrap, they are really good and it will help you sail through the development phase with regards to debugging emails.

      Create a Custom useFetch() React Hook

      Introduction

      A custom hook is a JavaScript function with a unique naming convention that requires –

      1. the function name to start with use and
      2. the function may call other Hooks

      The whole idea behind custom hooks is just so that we can extract component logic into reusable functions.

      Often times as we build out React applications, we see ourselves writing almost the same exact codes in two or more different components. Ideally what we could do in such cases would be to extract that recurrent logic into a reusable piece of code (hook) and reuse it where the need be.

      Before hooks, we share stateful logic between components using render props and higher-order components, however, since the introduction of hooks and since we came to understand how neat they make these concepts, it no longer made sense to keep using those. Basically, when we want to share logic between two JavaScript functions, we extract it to a third function possibly because both components and hooks are equally just functions.

      The rationale behind this move is not different from what we have already explained above. Compared to using the native fetch API out of the box, abstracting it into the useFetch hook gives us a one-liner ability, more declarative code style, reusable logic and an overall cleaner code as we’ll see in a minute. Consider this simple useFetch example:

      const useFetch = (url, options) => {
        const [response, setResponse] = React.useState(null);
        useEffect(async () => {
            const res = await fetch(url, options);
            const json = await res.json();
            setResponse(json);
        });
        return response;
      };
      

      Here, the effect hook called useEffect is used to perform major functions —

      1. fetch the data with the native fetch API and
      2. Set the data in the local state of the component with the state hook’s update function.

      Also, notice that the promise resolving happens with async/await.

      The effect hook runs on two occasions — When the component mounts and also when the component updates. What this means is, if nothing is done about the useFetch example above, we will most definitely run into a scary recurrent loop cycle. Why? Because we are setting the state after every data fetch, as a result, when we set the state, the component updates and the effect runs again.

      Obviously, this will result in an infinite data fetching loop and we don’t want that. What we do want, is to only fetch data when the component mounts and we have a neat way of doing it. All we have to do is provide an empty array as a second argument to the effect hook, this will stop it from activating on component updates but only when the component is mounted.

        useEffect(async () => {
            const res = await fetch(url, options);
            const json = await res.json();
            setResponse(json);
        }, []); 
      

      The second is an array containing all the variables on which the hook depends on. If any of the variables change, the hook runs again, but if the argument is an empty array, the hook doesn’t run when updating the component since there are no variables to watch.

      You may have noticed that in the effect hook above, we are using async/await to fetch data. However, according to documentation stipulations, every function annotated with async returns an implicit promise. So in our effect hook, we are returning an implicit promise whereas an effect hook should only return either nothing or a clean-up function.

      So by design, we are already breaking this rule because —

      1. We are not returning nothing
      2. A promise does not clean up anything

      As a result, if we go ahead with the code as is, we will get a warning in the console pointing out the fact that useEffect function must return a cleanup function or nothing.

      Warning: An effect function must not return anything besides a function, which is used for clean-up.
      
      It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:
      
      
      useEffect(() => {
        async function fetchData() {
          // You can await here
          const response = await MyAPI.getData(someId);
          // ...
        }
        fetchData();
      }, [someId]); // Or [] if effect doesn't need props or state
      
      Learn more about data fetching with Hooks: https://fb.me/react-hooks-data-fetching
      

      Simply put, using async functions directly in the useEffect() function is frowned at. What we can do to fix this is exactly what is recommended in the warning above. Write the async function and use it inside the effect.

      React.useEffect(() => {
          const fetchData = async () => {
            const res = await fetch(url, options);
            const json = await res.json();
            setResponse(json);
          };
          fetchData();
        }, []);
      

      Instead of using the async function directly inside the effect function, we created a new async function fetchData() to perform the fetching operation and simply call the function inside useEffect. This way, we abide by the rule of returning nothing or just a cleanup function in an effect hook. And if you should check back on the console, you won’t see any more warnings.

      One thing we haven’t mentioned or covered so far is how we can handle error boundaries in this concept. Well, it’s not complicated, when using async/await, it is common practice to use the good old try/catch construct for error handling and thankfully it will also work for us here.

      const useFetch = (url, options) => {
        const [response, setResponse] = React.useState(null);
        const [error, setError] = React.useState(null);
        React.useEffect(() => {
          const fetchData = async () => {
            try {
              const res = await fetch(url, options);
              const json = await res.json();
              setResponse(json);
            } catch (error) {
              setError(error);
            }
          };
          fetchData();
        }, []);
        return { response, error };
      };
      

      Here, we used the very popular JavaScript try/catch syntax to set and handle error boundaries. The error itself is just another state initialized with a state hook so whenever the hook runs, the error state resets. However, whenever there is an error state, the component renders feedback to the user or practically you can perform any desired operation with it.

      You may already know this, but I still feel that it’ll be helpful to point out that you can use hooks to handle loading states for your fetching operations. The good thing is, It’s just another state variable managed by a state hook so if we wanted to implement a loading state in our last example, we’ll set the state variable and update our useFetch() function accordingly.

      const useFetch = (url, options) => {
        const [response, setResponse] = React.useState(null);
        const [error, setError] = React.useState(null);
        const [isLoading, setIsLoading] = React.useState(false);
        React.useEffect(() => {
          const fetchData = async () => {
            setIsLoading(true);
            try {
              const res = await fetch(url, options);
              const json = await res.json();
              setResponse(json);
              setIsLoading(false)
            } catch (error) {
              setError(error);
            }
          };
          fetchData();
        }, []);
        return { response, error, isLoading };
          };
      

      We cannot complete this tutorial without working on a hands-on demonstration to put everything we’ve talked about in practice. Let’s build a mini-app that will fetch a bunch of dog images and their names. We’ll use useFetch to call the very good dog API for the data we’ll need for this app.

      First, we define our useFetch() function which is exactly the same as what we did before. We will simply reuse the one we created while demonstrating error handling above to explain the data fetching concept in practice since it already has most of the things we’ll need.

      const useFetch = (url, options) => {
        const [response, setResponse] = React.useState(null);
        const [error, setError] = React.useState(null);
        React.useEffect(() => {
          const fetchData = async () => {
            try {
              const res = await fetch(url, options);
              const json = await res.json();
              setResponse(json);
            } catch (error) {
              setError(error);
            }
          };
          fetchData();
        }, []);
        return { response, error };
      };
      

      Next, we create the App() function that will actually use our useFetch() function to request for the dog data that we need and display it on screen.

      function App() {
        const res = useFetch("https://dog.ceo/api/breeds/image/random", {});
        if (!res.response) {
          return <div>Loading...</div>
        }
        const dogName = res.response.status
        const imageUrl = res.response.message
        return (
          <div className="App">
            <div>
              <h3>{dogName}</h3>
              <div>
                <img src={imageUrl} alt="avatar" />
              </div>
            </div>
          </div>
        );
      }
      

      Here, we just passed the url into the useFetch() function with an empty options object to fetch the data for the cat. It’s really that simple, nothing elaborate or complex. Once we’ve fetched the data, we just extract it from the response object and display it on screen. Here’s a demo on Codesandbox:

      Data fetching has always been an issue to contend with when building frontend-end applications, this is usually because of all the edge cases that you will need to account for. In this post, we have explained and made a small demo to explain how we can declaratively fetch data and render it on screen by using the useFetch hook with the native fetch() API.

      Different Tricks on How to Make Bootstrap Columns All the Same Height

      Introduction

      Bootstrap 3 (and now Bootstrap 4) are amazing CSS frameworks that can make the lives of developers of any skill-level easier. When I was more of a beginner and I first started using Bootstrap, I used every feature of it possible and used to hack it to get things to work the way I wanted. Now, with more experience, I mostly just use their reset and grid system. I now rarely alter any of its core functionality.

      Bootstrap’s grid system is fantastic and near-perfect in my opinion. You can read about it here. I often see developers needing to match heights across columns while maintaining responsiveness. I’ve decided to share some of the methods I do to accomplish this, as well as some very cool tricks other developers and friends have taught me, and the general direction and solution that Bootstrap 4 is doing to address this common problem.

      I’ve made a demo CodePen to illustrate the issue when the content in columns is different lengths and how it messes with design. Some quick notes first:

      • Padding of 25px is added to the top and bottom of all Bootstrap stuff
      • A subtle border wraps all .cols
      • Various backgrounds are used to see how things stack on each other and how this all works

      The first solution I’m going to use is with JavaScript. This is pretty straightforward and simply uses JavaScript to match the heights of the columns. The best, easiest, and almost the most “official” JS way is to simply use matchHeight.

      There are definitely pros and cons to taking a JavaScript approach. With JavaScript, you get high cross-browser support, but you also have a bigger pageload and it won’t happen until the DOM is ready or loaded depending on when you trigger it. I like this approach though because I actually prefer to not have heights associated with my columns and instead the content in them.

      Here’s more info on matchHeight.js:

      The quickest way to get started is just reference the CDN link like so after your jQuery:

      <script src="//cdnjs.cloudflare.com/ajax/libs/jquery.matchHeight/0.7.0/jquery.matchHeight-min.js"><script>
      

      MatchHeight is super easy-to-use and essentially has two main options (among a bunch of other stuff):

      • Match heights on different rows
      • Match all heights

      Match heights on different rows

      Here’s how to match heights on different rows:

      $(function() {
        $('.box').matchHeight();
      });
      

      Match all heights

      Here’s how to match the height of all elements on the page:

      $(function() {
        $('.box').matchHeight(false);
      });
      

      Don’t forget responsive

      If you take either of these approaches, make sure to disable heights on mobile since the columns are all stacked it won’t matter if they’re the same height or not.

      You can just override the fixed height at the media query breakpoint. This changes based on xs, sm, md, or lg). Here’s a demo when using col-sm-*:

      @media only screen and (max-width : 767px) {
        .box {
          height: auto !important;
        }
      }
      

      The word “table” usually sets off a bunch of red flags with front-end developers, but it’s really not that bad when used right. A lot of people don’t even realize you can force divs and its elements to behave like a table element.

      Sometimes you want to do this because the table element’s columns have matched height as a default behavior. Here’s a CSS utility class to trick rows into thinking it’s a table when you’re using col-sm-* followed by a demo:

      @media only screen and (min-width : 768px) {
        .is-table-row {
          display: table;
        }
        .is-table-row [class*="col-"] {
          float: none;
          display: table-cell;
          vertical-align: top;
        }
      }
      
      <div class="row is-table-row">
        <div class="col-sm-4">...</div>
        <div class="col-sm-4">...</div>
        <div class="col-sm-4">...</div>
      </div>
      

      You’ll have to adjust this a bit based on what size column you’re using. So it would actually make sense to create multiple utility classes: is-xs-table-row, is-sm-table-row, is-md-table-row, and is-lg-table-row or just manually make sure you check for responsive.

      You’ll also notice I adjusted the styles a bit because the columns now have a height (not the custom .box element). If you take this approach, you’ll have to plan for this.

      This approach is really, really cool and probably the best solution for most people. I have no idea who came up with it, but it is super creative and has many benefits:

      • Works on responsive out-of-the-box with little thinking
      • Little effort from the developer to make sure it works well in various scenarios
      • Should work on all columns regardless of their sizing

      It also has a lot of cons though:

      • Positioning elements absolute to the bottom is not possible (see the CodePen adjusted styles on .btn). There are workarounds for this, but it’s unnatural a bit
      • Required to have “row” have overflow: hidden

      Here’s a utility class for it:

      .row.match-my-cols {
        overflow: hidden;
      }
      
      .row.match-my-cols [class*="col-"]{
        margin-bottom: -99999px;
        padding-bottom: 99999px;
      }
      
      <div class="row match-my-cols">
        <div class="col-sm-4">...</div>
        <div class="col-sm-4">...</div>
        <div class="col-sm-4">...</div>
      </div>
      

      How does it work?

      It adds 99999px of height to the column via padding and then uses the negative margin to force the position as if it is not there. Then the .row just hides anything that is overflowed.

      Flexbox is the CSS3 God’s gift to the world of grumpy front-end developers. It’s the ultimate tool for layouts and “gridding” via CSS. You can learn all about it with this Visual Guide to CSS3 Flexbox Properties.

      There’s only one problem. Internet Explorer browser support is awful. IE9 and below provides zero support, IE10 is a crapshoot with it, and IE11 has many bugs. It’s really only useful to a select few privileged developers, but know Flexbox is coming and here to stay.

      This method does equal heights, is super easy, is out-of-the-box responsive, and has everything you can ask for. Here’s a demo:

      .row.is-flex {
        display: flex;
        flex-wrap: wrap;
      }
      .row.is-flex > [class*='col-'] {
        display: flex;
        flex-direction: column;
      }
      
      
      .row.is-flex {
        display: -webkit-box;
        display: -webkit-flex;
        display: -ms-flexbox;
        display: flex;
        -webkit-flex-wrap: wrap;
        -ms-flex-wrap: wrap;
        flex-wrap: wrap;
      }
      
      .row.is-flex > [class*='col-'] {
        display: -webkit-box;
        display: -webkit-flex;
        display: -ms-flexbox;
        display: flex;
        -webkit-box-orient: vertical;
        -webkit-box-direction: normal;
        -webkit-flex-direction: column;
        -ms-flex-direction: column;
        flex-direction: column;
      }
      
      <div class="row is-flex">
        <div class="col-sm-4">...</div>
        <div class="col-sm-4">...</div>
        <div class="col-sm-4">...</div>
      </div>
      

      Bootstrap 4 will have two options for its grid: “With Flexbox” and “Without Flexbox”. If you opt-in with the Flexbox option, the heights are matched automatically. You can read more about it at What’s New in Bootstrap.

      Here’s an awesome demo showing the beauty of it:

      The problem is still browser support and Bootstrap 4 is, as of writing this, not production-ready and in alpha version.

      Bootstrap 4 also introduced a concept called “Cards”. Cards are defined as “a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options.”.

      You can read more about it here.

      Really, all it means is it gives you out-of-the-box Bootstrap styles for the .box demoed in these CodePens. This is really cool though because there are many options to match height on columns. The only thing is it’s not technically part of the “grid”, but is a phenomenal solution for matching heights of columns regardless.

      Here’s a demo:

      What’s cool about Cards in Bootstrap 4 is if you don’t opt-in with Flexbox, it will use tables to trick the heights of the columns to match. If you do, it will use Flexbox instead. This is one of the most exciting things about Bootstrap 4 in my opinion.

      Bootstrap is simply a framework. At the end of the day, it’s ultimately up to you or the developer to make it work the way you want with your design. You can use all these methods, some of these methods, or whatever. It really doesn’t matter so long you understand the pros and cons.

      I personally don’t like making CSS adjustments on any base bootstrap thing: .container, .row, .col-*-*. I think it’s too easy for developers to do unintentional things that alter the grid itself (like adding left or right margin or padding) and breaking the default functionality. It’s really up to you though!