layout
attribute.Jupyter interactive widgets have a layout
attribute exposing a number of CSS properties that impact how widgets are laid out.
height
width
max_height
max_width
min_height
min_width
visibility
display
overflow
overflow_x
(deprecated in 7.5
, use overflow
instead)overflow_y
(deprecated in 7.5
, use overflow
instead)border
margin
padding
top
left
bottom
right
object_fit
object_position
order
flex_flow
align_items
flex
align_self
align_content
justify_content
justify_items
grid_auto_columns
grid_auto_flow
grid_auto_rows
grid_gap
grid_template_rows
grid_template_columns
grid_template_areas
grid_row
grid_column
grid_area
You may have noticed that certain CSS properties such as margin-[top/right/bottom/left]
seem to be missing. The same holds for padding-[top/right/bottom/left]
etc.
In fact, you can atomically specify [top/right/bottom/left]
margins via the margin
attribute alone by passing the string '100px 150px 100px 80px'
for a respectively top
, right
, bottom
and left
margins of 100
, 150
, 100
and 80
pixels.
Similarly, the flex
attribute can hold values for flex-grow
, flex-shrink
and flex-basis
. The border
attribute is a shorthand property for border-width
, border-style (required)
, and border-color
.
The following example shows how to resize a Button
so that its views have a height of 80px
and a width of 50%
of the available space. It also includes an example of setting a CSS property that requires multiple values (a border, in thise case):
In [1]:
from ipywidgets import Button, Layout
b = Button(description='(50% width, 80px height) button',
layout=Layout(width='50%', height='80px', border='2px dotted blue'))
b
The layout
property can be shared between multiple widgets and assigned directly.
In [2]:
Button(description='Another button with the same layout', layout=b.layout)
In [3]:
b.layout.min_width='10%'
b.layout.max_width='20%'
In [4]:
from ipywidgets import Button, HBox, VBox
words = ['correct', 'horse', 'battery', 'staple']
items = [Button(description=w) for w in words]
left_box = VBox([items[0], items[1]])
right_box = VBox([items[2], items[3]])
HBox([left_box, right_box])
The Flexbox CSS specification is great for laying out items in a single direction, horizontally or vertically. As we saw in the previous example, two dimensional layout can be done with flexbox by using a combination of horizontal and vertical components.
The Grid CSS specifation is designed to be used for two dimensional layout. There are properties for specifying the number of items in each row or column, how they should be sized, and how items should be aligned.
The are notebooks with more detail about widgets and the Flexbox model and widgets and the Grid model. The code examples from each of those notebooks is included here also.
If you want to learn more about CSS layout after this tutorial, take a look at this excellent set of articles on CSS layout at MDN. The Flexbox and Grid articles each have links to more extensive guides at the end of the article.
The HBox
and VBox
classes above are special cases of the Box
widget.
The Box
widget enables the entire CSS flexbox spec as well as the Grid layout spec, enabling rich reactive layouts in the Jupyter notebook. It aims at providing an efficient way to lay out, align and distribute space among items in a container.
Again, the whole flexbox spec is exposed via the layout
attribute of the container widget (Box
) and the contained items. One may share the same layout
attribute among all the contained items.
The following flexbox tutorial on the flexbox layout follows the lines of the article A Complete Guide to Flexbox by Chris Coyier, and uses text and various images from the article with permission.
The flexbox layout spectrum is excellent for laying out items in a single direction, either horizontally or vertically.
Since flexbox is a whole module and not a single property, it involves a lot of things including its whole set of properties. Some of them are meant to be set on the container (parent element, known as "flex container") whereas the others are meant to be set on the children (known as "flex items"). If regular layout is based on both block and inline flow directions, the flex layout is based on "flex-flow directions". Please have a look at this figure from the specification, explaining the main idea behind the flex layout.
Basically, items will be laid out following either the main axis
(from main-start
to main-end
) or the cross axis
(from cross-start
to cross-end
).
main axis
- The main axis of a flex container is the primary axis along which flex items are laid out. Beware, it is not necessarily horizontal; it depends on the flex-direction property (see below).main-start | main-end
- The flex items are placed within the container starting from main-start and going to main-end.main size
- A flex item's width or height, whichever is in the main dimension, is the item's main size. The flex item's main size property is either the ‘width’ or ‘height’ property, whichever is in the main dimension.
cross axis - The axis perpendicular to the main axis is called the cross axis. Its direction depends on the main axis direction.cross-start | cross-end
- Flex lines are filled with items and placed into the container starting on the cross-start side of the flex container and going toward the cross-end side.cross size
- The width or height of a flex item, whichever is in the cross dimension, is the item's cross size. The cross size property is whichever of ‘width’ or ‘height’ that is in the cross dimension.The VBox
and HBox
helper classes provide simple defaults to arrange child widgets in vertical and horizontal boxes. They are roughly equivalent to:
def VBox(*pargs, **kwargs):
"""Displays multiple widgets vertically using the flexible box model."""
box = Box(*pargs, **kwargs)
box.layout.display = 'flex'
box.layout.flex_flow = 'column'
box.layout.align_items = 'stretch'
return box
def HBox(*pargs, **kwargs):
"""Displays multiple widgets horizontally using the flexible box model."""
box = Box(*pargs, **kwargs)
box.layout.display = 'flex'
box.layout.align_items = 'stretch'
return box
In [5]:
from ipywidgets import Layout, Button, Box
items_layout = Layout(width='auto') # override the default width of the button to 'auto' to let the button grow
box_layout = Layout(display='flex',
flex_flow='column',
align_items='stretch',
border='solid',
width='50%')
words = ['correct', 'horse', 'battery', 'staple']
items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]
box = Box(children=items, layout=box_layout)
box
Three buttons in an HBox. Items flex proportionally to their weight.
In [6]:
from ipywidgets import Layout, Button, Box, VBox
# Items flex proportionally to the weight and the left over space around the text
items_auto = [
Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),
Button(description='weight=3; auto', layout=Layout(flex='3 1 auto', width='auto'), button_style='danger'),
Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),
]
# Items flex proportionally to the weight
items_0 = [
Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),
Button(description='weight=3; 0%', layout=Layout(flex='3 1 0%', width='auto'), button_style='danger'),
Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),
]
box_layout = Layout(display='flex',
flex_flow='row',
align_items='stretch',
width='70%')
box_auto = Box(children=items_auto, layout=box_layout)
box_0 = Box(children=items_0, layout=box_layout)
VBox([box_auto, box_0])
A more advanced example: a reactive form.
The form is a VBox
of width '50%'. Each row in the VBox is an HBox, that justifies the content with space between..
In [7]:
from ipywidgets import Layout, Button, Box, FloatText, Textarea, Dropdown, Label, IntSlider
form_item_layout = Layout(
display='flex',
flex_flow='row',
justify_content='space-between'
)
form_items = [
Box([Label(value='Age of the captain'), IntSlider(min=40, max=60)], layout=form_item_layout),
Box([Label(value='Egg style'),
Dropdown(options=['Scrambled', 'Sunny side up', 'Over easy'])], layout=form_item_layout),
Box([Label(value='Ship size'),
FloatText()], layout=form_item_layout),
Box([Label(value='Information'),
Textarea()], layout=form_item_layout)
]
form = Box(form_items, layout=Layout(
display='flex',
flex_flow='column',
border='solid 2px',
align_items='stretch',
width='50%'
))
form
A more advanced example: a carousel.
In [8]:
from ipywidgets import Layout, Button, Box, Label
item_layout = Layout(height='100px', min_width='40px')
items = [Button(layout=item_layout, description=str(i), button_style='warning') for i in range(40)]
box_layout = Layout(overflow_x='scroll',
border='3px solid black',
width='500px',
height='',
flex_flow='row',
display='flex')
carousel = Box(children=items, layout=box_layout)
VBox([Label('Scroll horizontally:'), carousel])
Use the dropdowns and sliders in the widget to change the layout of the box containing the colored buttons. Many of the CSS layout options described above are available, and the Python code to generate a Layout
object reflecting the settings is in a TextArea
in the widget.
A few questions to answer after the demonstration of this (see the detailed flexbox guide for a longer discussion):
justify_content
affect? You may find it easier to answer this if you set wrap
to wrap
.align_items
affect? align_content
different than align_items
?
In [ ]:
from layout_preview import layout
layout
Four buttons in a box revisted: Change order and orientation
This example, from earlier in this notebook, lays out 4 buttons vertically.
Flexbox allows you to change the order and orientation of the children items in the flexbox without changing the children themselves.
flex_flow
so that the buttons are displayed in a single column in reverse order.flex_flow
so that the buttons are displayed in a single row instead of a column.align_items
and describe how it affects the display of the buttons. width
, then change flex_flow
to lay out the buttons in rows that wrap so that there is a 2x2 grid of buttons.Feel free to figure out the layout using the tool above and copy/paste the layout here!
In [ ]:
from ipywidgets import Layout, Button, Box
items_layout = Layout(width='auto') # override the default width of the button to 'auto' to let the button grow
box_layout = Layout(display='flex',
flex_flow='column',
align_items='stretch',
border='solid',
width='20%')
words = ['correct', 'horse', 'battery', 'staple']
items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]
box = Box(children=items, layout=box_layout)
box
Carousel revisted: item layout
The code that generated the carousel is reproduced below. Run the cell, then continue reading.
In [ ]:
from ipywidgets import Layout, Button, Box, Label
item_layout = Layout(height='100px', min_width='40px')
items = [Button(layout=item_layout, description=str(i), button_style='warning') for i in range(40)]
box_layout = Layout(overflow_x='scroll',
border='3px solid black',
width='500px',
height='',
flex_flow='row',
display='flex')
carousel = Box(children=items, layout=box_layout)
VBox([Label('Scroll horizontally:'), carousel])
To do:
min_width
for one of the items
, say the first one. Does it affect only the first one, or all of them? Why?height
of only the first button. Hint: It needs its own Layout
.
In [ ]:
items[0].layout.min_width = 'FILL IN WITH A WIDTH'
The GridBox
class is a special case of the Box
widget.
The Box
widget enables the entire CSS flexbox spec, enabling rich reactive layouts in the Jupyter notebook. It aims at providing an efficient way to lay out, align and distribute space among items in a container.
A more detailed description of the Grid layout is available.
The whole grid layout spec is exposed via the layout
attribute of the container widget (Box
) and the contained items. One may share the same layout
attribute among all the contained items.
The following flexbox tutorial on the flexbox layout follows the lines of the article A Complete Guide to Grid by Chris House, and uses text and various images from the article with permission.
To get started you have to define a container element as a grid with display: grid, set the column and row sizes with grid-template-rows, grid-template-columns, and grid_template_areas, and then place its child elements into the grid with grid-column and grid-row. Similarly to flexbox, the source order of the grid items doesn't matter. Your CSS can place them in any order, which makes it super easy to rearrange your grid with media queries. Imagine defining the layout of your entire page, and then completely rearranging it to accommodate a different screen width all with only a couple lines of CSS. Grid is one of the most powerful CSS modules ever introduced.
Before diving into the concepts of Grid it's important to understand the terminology. Since the terms involved here are all kinda conceptually similar, it's easy to confuse them with one another if you don't first memorize their meanings defined by the Grid specification. But don't worry, there aren't many of them.
Grid Container
The element on which display: grid
is applied. It's the direct parent of all the grid items. In this example container is the grid container.
<div class="container">
<div class="item item-1"></div>
<div class="item item-2"></div>
<div class="item item-3"></div>
</div>
Grid Item
The children (e.g. direct descendants) of the grid container. Here the item elements are grid items, but sub-item isn't.
<div class="container">
<div class="item"></div>
<div class="item">
<p class="sub-item"></p>
</div>
<div class="item"></div>
</div>
Grid Line
The dividing lines that make up the structure of the grid. They can be either vertical ("column grid lines") or horizontal ("row grid lines") and reside on either side of a row or column. Here the yellow line is an example of a column grid line.
Grid Track
The space between two adjacent grid lines. You can think of them like the columns or rows of the grid. Here's the grid track between the second and third row grid lines.
A more detailed description of the Grid layout is available. The Grid layout guide on MDN is also excellent.
In [ ]:
from ipywidgets import Button, GridBox, Layout, ButtonStyle
The first example defines a 3x3 grid and places 9 buttons into the grid.
In [ ]:
GridBox(children=[Button(description=str(i), layout=Layout(width='auto', height='auto'),
style=ButtonStyle(button_color='darkseagreen')) for i in range(9)
],
layout=Layout(
width='50%',
grid_template_columns='100px 50px 100px',
grid_template_rows='80px auto 80px',
grid_gap='5px 10px')
)
Add more buttons
Modify the code above to place more buttons in the GridBox
(do not modify the layout). Any number of buttons larger than 9 is fine.
The grid template defines a 3x3 grid. If additional children are placed in the grid their properties are determined by the layout properties grid_auto_columns
, grid_auto_rows
and grid_auto_flow
properties.
Set grid_auto_rows="10px"
and rerun the example with more than 9 buttons.
Set grid_auto_rows
so that the automatically added rows have the same format as the templated rows.
The grid can also be set up using a description words. The layout below defines a grid with 4 columns and 3 rows. The first row is a header, the bottom row is a footer, and the middle row has content in the first two columns, then an empty cell, followed by a sidebar.
Widgets are assigned to each of these areas by setting the widgets's layout grid_area
to the name of the area.
"header header header header"
"main main . sidebar "
"footer footer footer footer"
In [ ]:
header = Button(description='Header',
layout=Layout(width='auto', grid_area='header'),
style=ButtonStyle(button_color='lightblue'))
main = Button(description='Main',
layout=Layout(width='auto', grid_area='main'),
style=ButtonStyle(button_color='moccasin'))
sidebar = Button(description='Sidebar',
layout=Layout(width='auto', grid_area='sidebar'),
style=ButtonStyle(button_color='salmon'))
footer = Button(description='Footer',
layout=Layout(width='auto', grid_area='footer'),
style=ButtonStyle(button_color='olive'))
GridBox(children=[header, main, sidebar, footer],
layout=Layout(
width='50%',
grid_template_rows='auto auto auto',
grid_template_columns='25% 25% 25% 25%',
grid_template_areas='''
"header header header header"
"main main . sidebar "
"footer footer footer footer"
''')
)
Make the main area larger