Layout System
Understanding flexbox layouts and positioning in Sone
Layout System
Sone's layout system is based on Flexbox (using Yoga Layout) and provides powerful tools for creating responsive, flexible layouts.
Container Components
Column
Creates a vertical flex container (default flex direction is column):
import { Column, Text } from "sone";
Column(
Text("First item"),
Text("Second item"),
Text("Third item")
)
.gap(10)
.padding(20)
Row
Creates a horizontal flex container:
import { Row, Text } from "sone";
Row(
Text("Left"),
Text("Center"),
Text("Right")
)
.justifyContent("space-between")
.alignItems("center")
.padding(20)
Flexbox Properties
Flex Direction
Change the primary axis of layout:
// Vertical layout (default for Column)
Column(...)
.direction("column")
// Horizontal layout (default for Row)
Row(...)
.direction("row")
// Reversed layouts
Column(...).direction("column-reverse")
Row(...).direction("row-reverse")
Justify Content
Controls alignment along the main axis:
Row(
Text("Item 1"),
Text("Item 2"),
Text("Item 3")
)
.justifyContent("space-between") // "flex-start", "flex-end", "center",
// "space-between", "space-around", "space-evenly"
Align Items
Controls alignment along the cross axis:
Row(
Text("Short"),
Text("Taller\nText"),
Text("T")
)
.alignItems("center") // "flex-start", "flex-end", "center", "stretch", "baseline"
.height(100)
Align Content
Controls alignment of wrapped lines:
Row(
Text("Item 1"),
Text("Item 2"),
Text("Item 3"),
Text("Item 4")
)
.wrap("wrap")
.alignContent("space-around") // "flex-start", "flex-end", "center", "stretch",
// "space-between", "space-around", "space-evenly"
.width(200)
Flex Items
Flex Grow, Shrink, and Basis
Control how items grow and shrink:
Row(
Text("Fixed").width(100),
Text("Flexible").flex(1), // Takes remaining space
Text("2x Flexible").flex(2) // Takes 2x more space than flex(1)
)
.width(400)
More granular control:
Text("Content")
.grow(1) // How much to grow (default: 0)
.shrink(1) // How much to shrink (default: 1)
.basis(100) // Initial size before growing/shrinking
Align Self
Override alignItems for individual items:
Row(
Text("Top").alignSelf("flex-start"),
Text("Center").alignSelf("center"),
Text("Bottom").alignSelf("flex-end")
)
.alignItems("stretch") // Default for all items
.height(100)
Spacing
Gap
Space between flex items:
Row(
Text("Item 1"),
Text("Item 2"),
Text("Item 3")
)
.gap(20) // Same gap for both directions
.rowGap(15) // Vertical gap
.columnGap(25) // Horizontal gap
Padding
Internal spacing within components:
// Single value (all sides)
Text("Padded").padding(20)
// Two values (vertical, horizontal)
Text("Padded").padding(15, 30)
// Three values (top, horizontal, bottom)
Text("Padded").padding(10, 20, 15)
// Four values (top, right, bottom, left)
Text("Padded").padding(10, 20, 15, 25)
// Individual sides
Text("Padded")
.paddingTop(10)
.paddingRight(20)
.paddingBottom(15)
.paddingLeft(25)
Margin
External spacing around components:
// Same API as padding
Text("With margin").margin(20)
// Auto margins for centering
Text("Centered").margin("auto")
Text("Right aligned").marginLeft("auto")
Sizing
Width and Height
Text("Fixed size")
.width(200)
.height(100)
// Convenient size method (width, height)
Text("Square").size(100) // 100x100
Text("Rectangle").size(200, 100) // 200x100
// Percentage values
Text("Half width").width("50%")
// Auto sizing
Text("Auto width").width("auto")
Min/Max Constraints
Text("Constrained content")
.minWidth(100)
.maxWidth(300)
.minHeight(50)
.maxHeight(200)
Aspect Ratio
Maintain proportions:
Column(
Text("16:9 aspect ratio")
)
.aspectRatio(16/9)
.width(320) // Height will be 180
Positioning
Position Types
// Static positioning (default)
Text("Normal flow").position("static")
// Relative positioning
Text("Offset from normal position")
.position("relative")
.top(10)
.left(20)
// Absolute positioning
Text("Positioned relative to container")
.position("absolute")
.top(50)
.right(30)
Position Values
Text("Positioned")
.position("absolute")
.top(20) // Distance from top
.right(30) // Distance from right
.bottom(40) // Distance from bottom
.left(50) // Distance from left
// Percentage values
Text("Centered")
.position("absolute")
.top("50%")
.left("50%")
Convenient Position Shortcuts
Text("Shortcuts")
.start(10) // Left in LTR, right in RTL
.end(20) // Right in LTR, left in RTL
.inset(15) // All sides
Wrapping
Flex Wrap
Control line wrapping:
Row(
Text("Item 1").width(150),
Text("Item 2").width(150),
Text("Item 3").width(150),
Text("Item 4").width(150)
)
.wrap("wrap") // "nowrap", "wrap", "wrap-reverse"
.width(300) // Forces wrapping
Text Wrapping
Text("Long text that should wrap properly...")
.width(200)
.wrap(true) // Enable wrapping (default)
Text("No wrapping text")
.nowrap() // Disable wrapping
Box Model
Box Sizing
Control how width/height are calculated:
Text("Content box")
.boxSizing("content-box") // Width excludes padding/border (default)
Text("Border box")
.boxSizing("border-box") // Width includes padding/border
.padding(20)
.borderWidth(2)
.width(200) // Total width is 200px
Overflow
Control content overflow:
Text("Long content that might overflow the container...")
.width(100)
.height(50)
.overflow("hidden") // "visible", "hidden", "scroll"
Practical Layout Examples
Card Grid
function CardGrid(cards) {
return Row(
...cards.map(card =>
Column(
Text(card.title).weight("bold").size(16),
Text(card.content).size(14).color("gray")
)
.flex(1)
.padding(20)
.bg("white")
.rounded(8)
.margin(10)
)
)
.wrap("wrap")
.justifyContent("center");
}
Sidebar Layout
function SidebarLayout(sidebar, main) {
return Row(
Column(sidebar)
.width(250)
.bg("#f5f5f5")
.padding(20),
Column(main)
.flex(1)
.padding(30)
)
.height("100%");
}
Centered Content
function CenteredCard(content) {
return Column(
Column(content)
.maxWidth(400)
.padding(40)
.bg("white")
.rounded(12)
.shadow("0 4px 12px rgba(0,0,0,0.1)")
)
.justifyContent("center")
.alignItems("center")
.minHeight(400);
}
Header with Navigation
function Header(logo, navItems) {
return Row(
// Logo section
Row(logo).flex(0),
// Navigation section
Row(
...navItems.map(item =>
Text(item.label)
.size(16)
.color("black")
.padding(10, 15)
)
)
.gap(10)
.flex(1)
.justifyContent("center"),
// Actions section (empty but reserves space)
Row().flex(0).width(100)
)
.alignItems("center")
.padding(20)
.bg("white")
.shadow("0 2px 4px rgba(0,0,0,0.1)");
}
Responsive Container
function ResponsiveContainer(content) {
return Column(content)
.width("100%")
.maxWidth(1200) // Max width on large screens
.margin("auto") // Center horizontally
.padding(20, 40) // Responsive padding
}
Form Layout
function FormField(label, input) {
return Column(
Text(label).size(14).weight("500").marginBottom(8),
input
)
.marginBottom(20)
.width("100%");
}
function Form(fields, submitButton) {
return Column(
...fields,
Row(submitButton)
.justifyContent("flex-end")
.marginTop(20)
)
.maxWidth(400)
.margin("auto")
.padding(30);
}
Best Practices
Layout Strategy
- Start with the overall structure (rows/columns)
- Use flex properties for responsive behavior
- Apply spacing consistently with gap/padding/margin
Performance
- Avoid deeply nested layouts when possible
- Use flex properties instead of fixed sizes where appropriate
- Consider the layout reflow impact of dynamic content
Responsive Design
- Use percentage widths and max-widths
- Leverage flex grow/shrink for adaptable components
- Test layouts with different content sizes
Debugging
- Use the
tag()
method to identify components during development - Start simple and build complexity gradually
- Test edge cases (very long text, small containers, etc.)