Visitors get the page immediately — real text, real headings, real links, the page a search engine indexes — and Flutter takes it over once it is ready, without a blank screen. One tree in Dart, drawn twice: there is no second UI to keep in step.
Unibox
↓
HTML and CSS
the page a search engine reads: real text, real headings, real links
↓
Flutter
the same tree as widgets — a document until you touch it, then the application draws it. The handover is live in this frame
Make Flutter Web pages indexable
Flutter Web paints into a canvas, so a search engine is served an empty page: no text, no heading, no link to follow. Unibox writes the same tree as markup, so the page a crawler reads is the page a visitor lands on — and the app that takes it over is the one you already have.
the screen, in Dart
Box(
children: <UINode>[
TextNode(
'Apartments in Amsterdam',
headingLevel: 1,
),
Box(
url: 'apartments/amsterdam',
children: <UINode>[TextNode('See all')],
),
],
)
what a crawler is served
<body>
<div class="unibox-box-...">
<h1>Apartments in Amsterdam</h1>
<a href="apartments/amsterdam">
See all
</a>
</div>
</body>
class names elided
Flutter still takes the page overThe build is prefetched while the reader reads and boots into the page it was served, at the scroll and with the presses they already made. From there the dialogs, the forms and the screens behind the list are ordinary Flutter.
Adopted one page at a timeDescribe only the pages that have to be found; the rest of the application stays the Flutter it already is. Unibox makes the rendering crawlable and keeps the UI in one place — urls, copy, metadata, a sitemap and the deployment remain the application's.
Just boxes
A screen is containers: a box holds text, a picture, a field, or more boxes, and there is no widget or element to learn beside it. The four nodes this page had not drawn yet are the parts a card is made of — a field with no chrome, a stack, a picture, and a child placed at an offset. The list beside the card is that card, taken apart.
€ 1 850
Over the rooftops, two rooms72 m² · Paris · available now
the card, node by node
Box
crossAlign, gap
TextInputNode
placeholder
Box
radius, overflow
StackNode
height
ImageNode
src, alt, fit
PositionedNode
top, right
Box
radius, backgroundColor
TextNode
labelLarge
Box
padding, gap
TextNode
titleMedium
TextNode
bodySmall
the same tree, two calls
node.toWidget()
Flutter widgets
node.toHtml()
an HTML string
The hero, the catalogue and this bar are the same nodes, drawn by the same two calls.
Material
Buttons
Four shapes around one ink well: press one.
Filled
Tonal
Outlined
Text
Chips
A choice, and one with a cross.
Two rooms
Studio
Map
Sold
Garden
Balcony
Segments
A set of choices in one pill.
Day
Week
Month
List
Grid
Map
Text fields
The filled field, with its own focus, label and error.
We only use it for the receipt.
Email
We only use it for the receipt.
Cards and tiles
All six heights on the surface Material tints to match, the two other shapes a card comes in, and a row that says who it is.
Level 0surfaceContainerLowest · no shadow
Level 1surfaceContainerLow
Level 2surfaceContainer
Level 3surfaceContainerHigh
Level 4surfaceContainerHighest
Level 5surfaceContainerHighest · shadow only
Outlinedsurface · outlineVariant
Confirm
FilledsurfaceContainerHighest
AL
Ada Lovelace
ada@example.com
2m
Charles Babbage
charles@example.com
1h
Navigation bar
Three destinations; the chosen one sits on a pill.
Tabs
A tab bar over three pages: the bar says which tab is chosen and the node draws that page.
Overview
Listings
Reviews
Six flats, from the same tree.
A map beside them on a wide screen.
What the tenants said.
Secondary tabs
The same three tabs packed, each as wide as its label: the underline is the label's own width, not a share of the bar.
Overview
Listings
Reviews
Six flats, from the same tree.
Pages
A pager: `PagerNode` settles on one child at a time, and its children are the size the tree states. Swipe it sideways.
One
Two
Three
Scaffold
A screen: `MaterialScaffold` holds a bar, a body and a bottom bar, and only the body scrolls — the bars state a container and the page a surface, so a pull past the list opens inside the body and never at a bar’s edge. The second screen’s bar gives its height back instead: the collapse is a number a store hands the rebuild, which a document never runs.
An app bar that stays
Apartments
24
Garden loft
€1 280
Two rooms by the park
€1 460
Studio, sixth floor
€ 920
Attic with a terrace
€1 700
One room in the old town
€ 860
House on the canal
€2 100
Loft over the bakery
€1 180
Chambre de bonne
€ 640
An app bar that gives its height back
Saved
Garden loft
€1 280
Two rooms by the park
€1 460
Studio, sixth floor
€ 920
Attic with a terrace
€1 700
One room in the old town
€ 860
House on the canal
€2 100
Loft over the bakery
€1 180
Chambre de bonne
€ 640
App bars
The bar a page opens with, and the one a list scrolls under: `MaterialAppBar` states the height its slot keeps, the container it paints, and the `pin` that makes it stay.
A title, and a node at the end
Shortlist
12
A glyph before the title
Results
6 of 24
The large one, with air above its headline
Garden loft
Dialog
An OverlayNode: in the tree only while it is open.
Delete listing
Title, content and actions, on the surface a dialog is raised by. The button is a MaterialInkWell over a filled box, so a press leaves ink; the dialog is in the tree only while it is open.
Type scale
Display large
Sphinx of black quartz
Display medium
Sphinx of black quartz
Display small
Sphinx of black quartz
Headline large
Sphinx of black quartz
Headline medium
Sphinx of black quartz
Headline small
Sphinx of black quartz
Title large
Sphinx of black quartz
Title medium
Sphinx of black quartz
Title small
Sphinx of black quartz
Body large
Sphinx of black quartz
Body medium
Sphinx of black quartz
Body small
Sphinx of black quartz
Label large
Sphinx of black quartz
Label medium
Sphinx of black quartz
Label small
Sphinx of black quartz
Colour roles
primary#8C4A5D
onPrimary#FFFFFF
primaryContainer#FFD9E1
onPrimaryContainer#703346
secondary#75565D
onSecondary#FFFFFF
secondaryContainer#FFD9E1
onSecondaryContainer#5B3F46
surface#FFF8F8
onSurface#22191B
onSurfaceVariant#514346
surfaceContainerLowest#FFFFFF
surfaceContainerLow#FFF0F2
surfaceContainer#FBEAEC
surfaceContainerHigh#F5E4E6
surfaceContainerHighest#EFDFE1
outline#847376
outlineVariant#D6C2C5
error#BA1A1A
onError#FFFFFF
scrim#000000
Shapes
A path in a view box: a hexagon, a circle, and a ring — and that ring twice over, turning: a progress indicator and the spinner beside it.