When designing for the web, it is important to keep in mind what different users will reach you in different devices with different screen sizes and orientations.
In the early days of web design, pages were built to target a particular screen size.
If the user had a larger or smaller screen than the designer expected, results ranged from unwanted scroll bars to overly long line lengths, and poor use of space.
As more diverse screen sizes became available, the concept of responsive web design (RWD) appeared, a set of practices that allows web pages to alter their layout and appearance to suit different screen widths, resolutions, etc.
Responsive design
The term responsive design was coined by Ethan Marcotte in 2010 and described the use of multiple techniques:
Fluid grids, something which was already being explored by Gillenwater, and can be read up on in Marcotte’s article, Fluid Grids (published in 2009 on A List Apart).
Fluid images. Using a very simple technique of setting the max-width property to 100%, images would scale down smaller if their containing column became narrower than the image’s intrinsic size, but never grow larger. This enables an image to scale down to fit in a flexibly-sized column, rather than overflow it.
The third key component was the media query. Media Queries enable the type of layout switch that Cameron Adams had previously explored using JavaScript, using only CSS. Rather than having one layout for all screen sizes, the layout could be changed. Sidebars could be repositioned for the smaller screen, or alternate navigation could be displayed.
Media Queries
Responsive design was only able to emerge due to the media query. The Media Queries Level 3 specification became a Candidate Recommendation in 2009, meaning that it was deemed ready for implementation in browsers.
Media Queries allow us to run a series of tests (e.g. whether the user’s screen is greater than a certain width, or a certain resolution) and apply CSS selectively to style the page appropriately for the user’s needs.
Breakpoints
By defining a set “points” where these Media Queries will apply its different rules, we are effectively creating breakpoints where the styling and layout of the page changes. Many frontend frameworks reuse a set of tested and tried breakpoints, making that set of breakpoints its breakpoint system
.
Example bootstrap 5 breakpoints: - xs: Screen width from 0 to 576px - sm: Screen width above 576px - md: Screen width above 768px - lg: Screen width above 992px - xl: Screen width above 1200px - xxl: Screen width above 1400px
Keep in mind that users expect any website to be perfectly complementary with every single device they own – desktop, tablet, or mobile. If a website’s responsive design does not align with a certain device resolution (especially a commonly used device), the site is at risk of missing out on a segment of its target audience. Avoid this by investing time and research into defining breakpoints at the beginning of a project.
The amount of effort that goes into defining responsive breakpoints is directly proportional to the experience of the end-user.
Breakpoints in Shiny
If you are familiar with base shiny or other css based frameworks, you might have even used these systems without realizing;
For example, using the fluidRow()
function will trigger layout changes to your columns()
at specific screen sizes, based on bootstrap 3 breakpoints (The base CSS framework in shiny).
While its great to have this done automatically, it also comes with many constrains and does not allow for fine control of these layout changes.
Very often for more complex layouts, you may often find yourself writing additional CSS to add new behavior for specific elements or screen sizes.
Breakpoints with imola
Imola takes a slightly different approach to breakpoints:
- Out of the box it uses the same breakpoint system as base shiny (bootstrap 3).
- You can change the default breakpoint system at the application level, but also at a component level.
- For each
grid
andflex
function named attribute you are able to pass either avalue
for that attribute or anamed list of different values
for different breakpoints.
Names that can be used in function attributes depend on what breakpoint names are available, we can use getBreakpointSystem()
to see the active breakpoint system:
Imola Breakpoint System: bootstrap3
Name: No description
description
Breakpoints (name) Minimum screen size (px) Maximum screen size (px)
Available ----------------------------- ------------------------- -------------------------
NULL 575
xs NULL 767
sm NULL 991
md NULL 1199
lg 1200 NULL
xl -----------------------------
Using getBreakpointSystem()
with a name also allows us to get a specific registered breakpoint system, getBreakpointSystem("bulma")
returns a different breakpoint system that comes bundled with imola:
Imola Breakpoint System: bulma
Name: No description
description
Breakpoints (name) Minimum screen size (px) Maximum screen size (px)
Available ----------------------------- ------------------------- -------------------------
769 NULL
tablet 1024 NULL
desktop 1216 NULL
widescreen 1408 NULL
fullhd -----------------------------
For a full list of all breakpoint systems we can use listBreakpointSystems()
.
But how can we use these? Lets say we have the following gridPanel():
In out case, we want to use the default breakpoint system and target small devices, so we target these via xs
, and build our areas
argument as a named list instead.
We can use default
as a name for our default value for the areas
argument. Think of default
as the value used for screen sizes where no other value can be applied.
default
is a reserved keyword in imola, so keep in mind not to use it when editing or creating a custom breakpoint system.
gridPanel(
areas = list(
default = c(
"area1 area1 area1",
"area2 area3 area3",
"area2 area3 area3"
),
xs = c(
"area1",
"area2",
"area3"
)
),
...
)
Switching to a different breakpoint system would change this syntax slightly.
We can either change the default global system with setActiveBreakpointSystem(name)
or change it for this specific gridPanel()
.
Changing the global system would affect all panels that are currently using breakpoints, so picking a global system should be something you do before adding breakpoints to your arguments.
As a workaround we can define a different system for this panel only:
gridPanel(
areas = c(
"area1 area1 area1",
"area2 area3 area3",
"area2 area3 area3"
),
...,
breakpoint_system = getBreakpointSystem("bulma")
)
And use the available names in that system instead:
gridPanel(
areas = list(
tablet = c(
"area1 area1 area1",
"area2 area3 area3",
"area2 area3 area3"
),
default = c(
"area1",
"area2",
"area3"
)
),
...,
breakpoint_system = getBreakpointSystem("bulma")
)
In this case the bulma system is mobile first, meaning default
will apply for mobile screen and tablet
for any screen width above 769px
.
All gridPanel()
and flexPanel()
arguments that affect the styling of the panel allow this behavior for the use of breakpoints. See each function documentation for more details.
Extending breakpoint systems
It is also possible to edit a breakpoint system and add or remove breakpoints. To do this we must first retrieve a registered system using getBreakpointSystem(name)
.
This returns a object version of that system,
> obj <- getBreakpointSystem()
> obj
Imola Breakpoint System: bootstrap3
Name: No description
description
Breakpoints (name) Minimum screen size (px) Maximum screen size (px)
Available ----------------------------- ------------------------- -------------------------
NULL 575
xs NULL 767
sm NULL 991
md NULL 1199
lg 1200 NULL
xl -----------------------------
We can now edit this system by adding or removing breakpoints. To create a new breakpoint lets call breakpoint()
:
> breakpoint("mybreakpoint", min = 300, max = 400)
Imola Breakpoint: mybreakpoint
Name
:
Affect Screen Sizes: 300 px
Minimum: 400 px Maximum
This creates a breakpoint that can than be added to our system with addBreakpoint()
:
> obj <- addBreakpoint(obj, breakpoint("mybreakpoint", min = 300, max = 400))
Imola Breakpoint System: bootstrap3
Name: No description
description
Breakpoints (name) Minimum screen size (px) Maximum screen size (px)
Available ----------------------------- ------------------------- -------------------------
NULL 575
xs NULL 767
sm NULL 991
md NULL 1199
lg 1200 NULL
xl 300 400
mybreakpoint -----------------------------
We can also remove a breakpoint by name using removeBreakpoint()
This creates a breakpoint that can than be added to our system with addBreakpoint()
:
> obj <- removeBreakpoint(obj, "xl")
Imola Breakpoint System: bootstrap3
Name: No description
description
Breakpoints (name) Minimum screen size (px) Maximum screen size (px)
Available ----------------------------- ------------------------- -------------------------
NULL 575
xs NULL 767
sm NULL 991
md NULL 1199
lg 300 400
mybreakpoint -----------------------------
After we finish editing our system we can now use it either in the object for as an argument:
gridPanel(
...,
breakpoint_system = obj
)
Or register it for global usage:
registerBreakpointSystem(obj)
gridPanel(
...,
breakpoint_system = getBreakpointSystem("bootstrap3")
)
NOTE: Registering a breakpoint system will overwrite a system with the same name.
If you would like to unregister a breakpoint system, you can also use unregisterBreakpointSystem()
.
Creating a new breakpoint system
Breakpoint systems can also be created from scratch using breakpointSystem()
> breakpointSystem("mysystem", breakpoint("mybreakpoint", min = 300, max = 400))
Imola Breakpoint System: mysystem
Name: No description
description
Breakpoints (name) Minimum screen size (px) Maximum screen size (px)
Available ----------------------------- ------------------------- -------------------------
300 400
mybreakpoint -----------------------------
All functionality to add or remove breakpoints, register and unregister the system, using it as a argument value can also be used with this object.
Importing and Exporting
It is also possible to import and export breakpoint system objects for future usage in different projects. In this case you can make use of exportBreakpointSystem()
and importBreakpointSystem()
.
exportBreakpointSystem()
allows you to export a system object into a specific file.
This file will contain all the necessary information to rebuild the system, even in a different project, and can be turned back into a template object using importBreakpointSystem()
.
After importing all functionality to add or remove breakpoints, register and unregister the system, using it as a argument value can also be used with this object.
Best Practices for adding Responsive Breakpoints
Develop for mobile-first – By developing and designing mobile-first content, the developer and designer receive multiple benefits. It is more difficult to simplify a desktop experience for mobile screens than it is to expand a mobile view for desktop screens. When a design is mobile-first, developers address what is most necessary, and can then make additions to match the preferences of desktop users.
Always keep major breakpoints in mind. This usually means common screen sizes (480px, 768px, 1024px, and 1280px).
Before choosing major breakpoints, use website analytics to discern the most commonly used devices from which your site is accessed. Add breakpoints for those screen sizes first.
An intelligent method is to hide or display elements at certain breakpoints. If necessary, switch content or features at breakpoints. For example, consider implementing off-canvas navigation for smaller screens and a typical navigation bar for larger ones.
Don’t define standard breakpoints for responsive design on the basis of device size. The primary objective of responsive design breakpoints is to display content in the best possible way. So, let the content be the guide. Add a breakpoint when the content and design requires it.