Build responsive GUI layouts that work across different screen sizes and orientations using the FlexBox and Grid classes.
LEVEL: Intermediate
PLATFORMS: Windows, macOS, Linux, iOS, Android
CLASSES: FlexBox, FlexItem, Grid, GridItem
This tutorial assumes understanding of simple layout techniques using the Rectangle class as explained in Tutorial: Advanced GUI layout techniques. If you haven't done so already, you should read that tutorial first.
Download the demo project for this tutorial here: PIP | ZIP . Unzip the project and open the first header file in the Projucer.
If you need help with this step, see Tutorial: Projucer Part 1: Getting started with the Projucer.
The demo project demonstrates different responsive layout techniques using FlexBox and Grid objects when dealing with variable screen sizes and resolutions. If we first run the project in its initial state, it should look something like this:

Right now, the layout uses common non-responsive techniques to lay out the components on the screen and does not accomodate for orientation changes. We will make use of the FlexBox and Grid items to eradicate these problems.
The FlexBox and Grid classes are highly inspired by the responsive layout practices used in CSS web development. If you have designed a responsive website before, you should be familiar with the layout systems described in this section.
When using FlexBox, we first need to define the direction of layout as horizontal or vertical and every subsequent computation will be executed on this basis. We call this direction the main axis and its perpendicular counterpart the cross axis. Based on this information the following properties will affect the layout as follows:
The items inside the container are defined by the FlexItem class and have 3 flexible properties that affect its dynamic resizing:
As a two-dimensional layout system, Grid works on both the row axis and the column axis. Similarly to FlexBox, the following properties will affect the layout as follows:
GridItem objects are contained within the Grid and have useful properties that affect its size:
Now that we know how specific properties can affect these layout systems we can start implementing those changes in our demo project.
Let's start by replacing the button layout in the RightSidePanel::resized() method using FlexBox:
void resized() override
{
juce::FlexBox fb; // [1]
fb.flexWrap = juce::FlexBox::Wrap::wrap; // [2]
fb.justifyContent = juce::FlexBox::JustifyContent::center; // [3]
fb.alignContent = juce::FlexBox::AlignContent::center; // [4]
for (auto* b : buttons) // [5]
fb.items.add (juce::FlexItem (*b).withMinWidth (50.0f).withMinHeight (50.0f));
fb.performLayout (getLocalBounds()); // [6]
}
withMaxWidth() and withMaxHeight() methods respectively.performLayout() method.As for the rotary slider layout on the left side panel, we adjust our LeftSidePanel::resized() method accordingly:
void resized() override
{
//==============================================================================
juce::FlexBox knobBox;
knobBox.flexWrap = juce::FlexBox::Wrap::wrap;
knobBox.justifyContent = juce::FlexBox::JustifyContent::spaceBetween; // [1]
for (auto* k : knobs)
knobBox.items.add (juce::FlexItem (*k).withMinHeight (50.0f).withMinWidth (50.0f).withFlex (1)); // [2]
//==============================================================================
juce::FlexBox fb; // [3]
fb.flexDirection = juce::FlexBox::Direction::column;
fb.items.add (juce::FlexItem (knobBox).withFlex (2.5)); // [4]
fb.performLayout (getLocalBounds());
}
JustifyContent::spaceBetween property.1 . The flex-grow factor determines the amount of space inside the container that the item should take up.Direction::column property.2.5 .Nesting FlexBox objects allows us to create intricate responsive layouts with ease by encapsulating smaller groups of Components together.
Lastly, we can deal with the main panel sliders by making them responsive to orientation changes in the MainPanel::resized() method:
void resized() override
{
auto isPortrait = getLocalBounds().getHeight() > getLocalBounds().getWidth(); // [1]
juce::FlexBox fb;
fb.flexDirection = isPortrait ? juce::FlexBox::Direction::column // [2]
: juce::FlexBox::Direction::row;
for (auto* s : sliders)
{
s->setSliderStyle (isPortrait ? juce::Slider::SliderStyle::LinearHorizontal // [3]
: juce::Slider::SliderStyle::LinearVertical);
fb.items.add (juce::FlexItem (*s).withFlex (0, 1, isPortrait ? (float) getHeight() / 5.0f // [4]
: (float) getWidth() / 5.0f));
}
fb.performLayout (getLocalBounds());
}
The sliders will now accomodate to the device orientation and adjust direction accordingly.
Finally, we can change the overall layout system of our panels to use flex as well:
void resized() override
{
juce::FlexBox fb;
juce::FlexItem left ((float) getWidth() / 4.0f, (float) getHeight(), leftPanel);
juce::FlexItem right ((float) getWidth() / 4.0f, (float) getHeight(), rightPanel);
juce::FlexItem main ((float) getWidth() / 2.0f, (float) getHeight(), mainPanel);
fb.items.addArray ({ left, main, right });
fb.performLayout (getLocalBounds());
}
If we run our newly-modified code to use flex, we should see something like this:

The source code for this modified version of the code can be found in the FlexBoxGridTutorial_02.h file of the demo project.
Let's try to implement the last portion of code using the Grid class instead. Here we create a Grid object to perform our layout operations on, just like flex:
void resized() override
{
juce::Grid grid;
using Track = juce::Grid::TrackInfo;
using Fr = juce::Grid::Fr;
grid.templateRows = { Track (Fr (1)) };
grid.templateColumns = { Track (Fr (1)), Track (Fr (2)), Track (Fr (1)) };
grid.items = { juce::GridItem (leftPanel), juce::GridItem (mainPanel), juce::GridItem (rightPanel) };
grid.performLayout (getLocalBounds());
}
However instead of specifying the flex-grow, flex-shrink and flex-basis values on the individual FlexItem objects, in this case we set the number of rows and columns on the Grid object using TrackInfo objects. The constraints can be specified in fractions or pixels by using the _fr and _px suffixes respectively. In this example we define a grid with 1 row and 3 columns with the center column taking twice as much space as the others.
Pixels in JUCE are not equivalent to physical pixels. Internal calculations convert the pixel density depending on the screen DPI resolution.
The source code for this modified version of the code can be found in the FlexBoxGridTutorial_03.h file of the demo project.
There are many cases where either of these classes can be used to create responsive layouts. However there are certain scenarios where one is more suitable and sometimes even necessary to solve certain layout constraints.
Some of the advantages of the FlexBox class:
Some of the advantages of the Grid class:
Exercise: Implement the previous FlexBox layouts using the Grid class instead. Were there any inconvenient use cases where the FlexBox class was more suitable?
In this tutorial, we have learnt how to design responsive layouts using the FlexBox and Grid classes. In particular, we have: