Core Elements
In Part 1, you built your first Freya application using rect() and label(). In this tutorial, we’ll explore all five core elements in depth - the fundamental building blocks that every Freya UI is made of.
[!NOTE] Think Like Lego Every complex interface you’ve ever seen is built from simple elements. A Facebook feed? Rectangles with images and labels. A Spotify player? Rectangles with images, labels, and SVG icons. Master these basics, and you can build anything.
The Five Core Elements
Freya provides five fundamental elements:
| Element | Purpose | HTML Equivalent |
|---|---|---|
rect() | Container for other elements | <div> |
label() | Simple text display | <span> |
paragraph() | Advanced text with formatting | <p> with <span> |
svg() | Vector graphics and icons | <svg> |
image() | Display images | <img> |
Everything you see in a Freya app is built by combining these five elements. Let’s explore each one.
rect() - The Container Element
rect() is the most important element. It’s a rectangular container that holds other elements, like a box.
Why Do We Need Containers?
Think of a webpage layout:
- A header at the top (a rectangle)
- A sidebar on the left (a rectangle)
- Main content in the center (a rectangle)
- Each card in the content (a rectangle)
- Each button (a rectangle)
Rectangles within rectangles - that’s how GUIs work!
Basic rect() Usage
rect()
.width(Size::px(200.0)) // 200 pixels wide
.height(Size::px(100.0)) // 100 pixels tall
.background(Color::BLUE) // Blue background
.child(label().text("Hi!")) // Content inside
This creates a 200x100 blue rectangle with text inside.
Sizing Options
Freya provides several ways to specify sizes:
// Fixed size in pixels
rect()
.width(Size::px(200.0))
.height(Size::px(100.0))
// Fill available space
rect()
.width(Size::fill()) // "Take all width available"
.height(Size::fill()) // "Take all height available"
// Auto size based on content
rect()
.width(Size::auto()) // "Size to fit my children"
.height(Size::auto())
// Percentage of parent
rect()
.width(Size::percent(50.0)) // 50% of parent's width
.height(Size::percent(25.0)) // 25% of parent's height
[!TIP] Quick Shorthands Instead of
.width(Size::fill()).height(Size::fill()), you can use:rect() .expanded() // Same as width: fill, height: fill
Size Constraints
Control minimum and maximum sizes:
rect()
.width(Size::fill())
.min_width(Size::px(200.0)) // At least 200px wide
.max_width(Size::px(600.0)) // At most 600px wide
.height(Size::auto())
.min_height(Size::px(50.0)) // At least 50px tall
This is useful for responsive layouts that need to work at different window sizes.
Padding and Margin
Padding is space inside the element (between border and content):
rect()
.padding(16.0) // 16px on all sides
.padding_horizontal(24.0) // Left and right: 24px
.padding_vertical(8.0) // Top and bottom: 8px
Margin is space outside the element (between this element and siblings):
rect()
.margin(16.0) // 16px on all sides
.margin_horizontal(24.0) // Left and right: 24px
.margin_vertical(8.0) // Top and bottom: 8px
[!NOTE] Visualizing Padding vs Margin
┌─────────────────────────┐ ← Element border │ MARGIN │ ← Space outside │ ┌─────────────────┐ │ │ │ PADDING │ │ ← Space inside │ │ ┌───────────┐ │ │ │ │ │ CONTENT │ │ │ │ │ └───────────┘ │ │ │ └─────────────────┘ │ └─────────────────────────┘
Rounded Corners
rect()
.corner_radius(8.0) // All corners: 8px radius
.corner_radius(16.0, 4.0, 16.0, 4.0) // TL, TR, BR, BL individually
Larger values = more rounded. A corner_radius of half the height creates a “pill” shape:
rect()
.height(Size::px(40.0))
.corner_radius(20.0) // Perfect pill/button shape
Background Colors
rect()
.background(Color::WHITE) // Named color
.background(Color::from_rgb(255, 0, 0)) // RGB
.background(Color::from_hex("#ff6600").unwrap()) // Hex
Borders
rect()
.border(
Border::new()
.width(2.0) // 2 pixels thick
.fill(Color::BLACK) // Black color
)
Shadows
Shadows add depth and make elements “pop”:
rect()
.shadow(
Shadow::new()
.color(Color::BLACK) // Shadow color
.x(0.0) // Horizontal offset
.y(4.0) // Vertical offset (down)
.blur(10.0) // How blurry
)
[!TIP] Realistic Shadow Recipe For a natural-looking shadow, use:
- Dark color with transparency:
Color::BLACK.with_a(50)- Small Y offset (2-6 pixels)
- Blur radius of 8-20 pixels
Visual Effects
Apply visual transformations and effects to your rectangles:
// Opacity (0.0 = invisible, 1.0 = fully visible)
rect()
.opacity(0.5) // 50% transparent
// Rotation (degrees)
rect()
.rotate(45.0) // Rotate 45 degrees
// Scale (enlarge or shrink)
rect()
.scale(Scale::new(1.5)) // 150% size
// Blur effect
rect()
.blur(5.0) // Apply blur filter
// Overflow handling
rect()
.overflow(Overflow::Clip) // Clip content to bounds
.overflow(Overflow::Scroll) // Make scrollable
Position Control
Control how elements are positioned:
// Relative positioning (default) - flows with layout
rect()
.position(Position::Relative)
// Absolute positioning - placed at exact coordinates
rect()
.position(Position::Absolute)
.left(Size::px(10.0)) // 10px from left
.top(Size::px(20.0)) // 20px from top
// Layer ordering (higher values appear on top)
rect()
.z_index(1) // Above z_index(0)
[!NOTE] When to Use Absolute Positioning Use
Position::Absolutesparingly - for overlays, tooltips, modals, or floating elements. Most layouts should use relative positioning with flexbox.
Alignment Shorthand Methods
Freya provides convenient shorthand methods for common alignment patterns:
// Center content both horizontally and vertically
rect()
.center() // Same as main_align_center() + cross_align_center()
// Center on one axis only
rect()
.center_x() // Center horizontally
.center_y() // Center vertically
// Fill available space
rect()
.fill_width() // width: Size::fill()
.fill_height() // height: Size::fill()
// Fill everything
rect()
.expanded() // width + height: fill
Complete rect() Example: A Card
fn card() -> impl IntoElement {
rect()
.width(Size::px(300.0))
.background(Color::WHITE)
.corner_radius(12.0)
.padding(16.0)
.border(Border::new().width(1.0).fill(Color::LIGHT_GRAY))
.shadow(
Shadow::new()
.color(Color::BLACK.with_a(30))
.y(4.0)
.blur(12.0)
)
.child(
label()
.text("Card Title")
.font_size(20.0)
.font_weight(FontWeight::Bold)
)
}
label() - Simple Text
label() displays a single line of text. It’s the simplest way to show text.
Basic Usage
label()
.text("Hello, World!")
Text Styling
label()
.text("Styled Text")
.font_size(24.0) // Size in points
.color(Color::RED) // Text color
.font_weight(FontWeight::Bold) // Bold text
.font_family("Inter") // Font name
.line_height(1.5) // Spacing between lines
Font Weights
FontWeight::Thin // 100 - Very thin
FontWeight::Light // 300
FontWeight::Normal // 400 - Default
FontWeight::Medium // 500
FontWeight::SemiBold // 600
FontWeight::Bold // 700 - Bold
FontWeight::ExtraBold // 800
FontWeight::Black // 900 - Very thick
Text Alignment
label()
.text("Aligned Text")
.text_align(TextAlign::Start) // Left (in LTR languages)
.text_align(TextAlign::Center) // Centered
.text_align(TextAlign::End) // Right (in LTR languages)
Limiting Lines
label()
.text("Very long text that might not fit...")
.max_lines(1) // Only show one line
When to Use label() vs paragraph()
Use label() When | Use paragraph() When |
|---|---|
| Single style of text | Mixed styles (bold + normal) |
| Short, simple text | Multiple spans needed |
| UI elements like buttons | Rich text formatting |
paragraph() - Rich Text
paragraph() is for advanced text with mixed formatting. Think of it like writing in a word processor where different words can have different styles.
The Problem with label()
What if you want “Hello, World!” where “World” is bold but “Hello, ” isn’t? You can’t do that with label().
The Solution: paragraph() with Spans
paragraph()
.child(Span::new("Hello, ").color(Color::BLACK))
.child(Span::new("World!").color(Color::RED).font_weight(FontWeight::Bold))
Each Span is a piece of text with its own styling.
Span Styling Options
Span::new("styled text")
.color(Color::RED) // Text color
.font_size(20.0) // Size
.font_weight(FontWeight::Bold) // Weight
.font_style(FontStyle::Italic) // Italic
.font_family("Arial") // Font
Complete paragraph() Example
fn formatted_text() -> impl IntoElement {
paragraph()
.font_size(16.0)
.line_height(1.6)
.child(Span::new("This is ").color(Color::BLACK))
.child(
Span::new("bold text")
.font_weight(FontWeight::Bold)
.color(Color::BLACK)
)
.child(Span::new(" and this is ").color(Color::BLACK))
.child(
Span::new("italic")
.font_style(FontStyle::Italic)
.color(Color::GRAY)
)
.child(Span::new(".").color(Color::BLACK))
}
Paragraph-Level Styling
Some styles apply to the whole paragraph:
paragraph()
.font_size(16.0) // Default size for all spans
.color(Color::BLACK) // Default color
.line_height(1.5) // Line spacing
.max_lines(3) // Maximum lines (ellipsis for overflow)
.text_align(TextAlign::Center) // Alignment
Practical Example: A Link
fn link(text: &str) -> impl IntoElement {
paragraph()
.child(
Span::new(text)
.color(Color::BLUE)
.font_weight(FontWeight::Medium)
)
// Note: You'd add .on_pointer_down() for click handling
}
svg() - Vector Graphics
svg() displays SVG (Scalable Vector Graphics) - perfect for icons and illustrations.
What is SVG?
SVG is a format for graphics defined as code (XML), not pixels. Benefits:
- Infinitely scalable - no pixelation at any size
- Tiny file sizes - ideal for icons
- Colorable - you can change colors programmatically
Basic svg() Usage
// SVG content as a string
let svg_content = r#"
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
</svg>
"#;
svg()
.svg_data(svg_content)
.width(Size::px(24.0))
.height(Size::px(24.0))
.color(Color::BLUE) // Tint color
Loading SVG from File
svg()
.svg_resource("icons/logo.svg") // Path relative to project
.width(Size::px(48.0))
.height(Size::px(48.0))
[!TIP] Where to Get SVG Icons
- Heroicons - Beautiful, free icons
- Feather Icons - Simple, elegant icons
- Lucide - Fork of Feather with more icons
Common Icon Example
Here’s a simple “plus” icon:
fn plus_icon() -> impl IntoElement {
let svg_data = r#"
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
"#;
svg()
.svg_data(svg_data)
.width(Size::px(24.0))
.height(Size::px(24.0))
.color(Color::BLACK)
}
Using SVG in Buttons
fn icon_button() -> impl IntoElement {
let icon_svg = r#"<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>"#;
rect()
.width(Size::px(40.0))
.height(Size::px(40.0))
.background(Color::BLUE)
.corner_radius(8.0)
.main_align_center()
.cross_align_center()
.child(
svg()
.svg_data(icon_svg)
.width(Size::px(24.0))
.height(Size::px(24.0))
.color(Color::WHITE)
)
}
image() - Displaying Images
image() displays raster images (PNG, JPEG, etc.).
Basic Usage with Bytes
// Load image bytes (from include_bytes! at compile time)
let image_bytes = include_bytes!("../../assets/photo.png");
image()
.image_data(image_bytes)
.width(Size::px(200.0))
.height(Size::px(150.0))
Loading from File Path
image()
.image_resource("assets/photo.png")
.width(Size::px(200.0))
.height(Size::px(150.0))
Image Fitting Options
Control how images fill their container:
image()
.image_data(image_bytes)
.width(Size::px(200.0))
.height(Size::px(200.0))
.image_fill(ImageFill::Cover) // Cover the area, crop if needed
Common options:
ImageFill::Cover- Fill the area completely, may crop imageImageFill::Contain- Show entire image, may have empty space
Practical Example: Avatar
fn avatar(image_bytes: &'static [u8]) -> impl IntoElement {
rect()
.width(Size::px(48.0))
.height(Size::px(48.0))
.corner_radius(24.0) // Circular
.overflow(Overflow::Clip) // Clip to corners
.child(
image()
.image_data(image_bytes)
.width(Size::fill())
.height(Size::fill())
.image_fill(ImageFill::Cover)
)
}
Combining Elements - Real World Examples
Now let’s combine all five elements to create real UI components.
Example 1: User Card
A card showing a user’s profile:
fn user_card() -> impl IntoElement {
let avatar_svg = r#"<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>"#;
rect()
.width(Size::px(280.0))
.background(Color::WHITE)
.corner_radius(12.0)
.padding(16.0)
.direction(Direction::Horizontal)
.gap(12.0)
.child(
// Avatar
rect()
.width(Size::px(56.0))
.height(Size::px(56.0))
.corner_radius(28.0)
.background(Color::LIGHT_GRAY)
.main_align_center()
.cross_align_center()
.child(
svg()
.svg_data(avatar_svg)
.width(Size::px(32.0))
.height(Size::px(32.0))
.color(Color::GRAY)
)
)
.child(
// User info
rect()
.width(Size::fill())
.direction(Direction::Vertical)
.gap(4.0)
.child(
label()
.text("Jane Doe")
.font_size(16.0)
.font_weight(FontWeight::Bold)
)
.child(
label()
.text("Software Engineer")
.font_size(14.0)
.color(Color::GRAY)
)
.child(
label()
.text("[email protected]")
.font_size(12.0)
.color(Color::LIGHT_GRAY)
)
)
}
Example 2: Notification Item
A notification with an icon, title, and timestamp:
fn notification() -> impl IntoElement {
let bell_svg = r#"<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>
</svg>"#;
rect()
.width(Size::px(350.0))
.background(Color::WHITE)
.padding(12.0)
.direction(Direction::Horizontal)
.gap(12.0)
.border(Border::new().width(1.0).fill(Color::LIGHT_GRAY))
.corner_radius(8.0)
.child(
// Icon
rect()
.width(Size::px(40.0))
.height(Size::px(40.0))
.background(Color::from_rgb(230, 245, 255))
.corner_radius(20.0)
.main_align_center()
.cross_align_center()
.child(
svg()
.svg_data(bell_svg)
.width(Size::px(20.0))
.height(Size::px(20.0))
.color(Color::from_rgb(0, 120, 200))
)
)
.child(
// Content
rect()
.width(Size::fill())
.direction(Direction::Vertical)
.gap(4.0)
.child(
paragraph()
.child(Span::new("New message from ").color(Color::BLACK))
.child(Span::new("John").font_weight(FontWeight::Bold).color(Color::BLACK))
)
.child(
label()
.text("2 minutes ago")
.font_size(12.0)
.color(Color::GRAY)
)
)
}
Example 3: Product Card
An e-commerce style product card:
fn product_card() -> impl IntoElement {
// Placeholder image (a colored rectangle in this example)
let star_svg = r#"<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
</svg>"#;
rect()
.width(Size::px(220.0))
.background(Color::WHITE)
.corner_radius(12.0)
.overflow(Overflow::Clip)
.shadow(
Shadow::new()
.color(Color::BLACK.with_a(40))
.y(2.0)
.blur(8.0)
)
.child(
// Product image placeholder
rect()
.height(Size::px(160.0))
.width(Size::fill())
.background(Color::from_rgb(240, 240, 240))
.main_align_center()
.cross_align_center()
.child(
label()
.text("Product Image")
.color(Color::GRAY)
)
)
.child(
// Product info
rect()
.padding(12.0)
.direction(Direction::Vertical)
.gap(8.0)
.child(
label()
.text("Wireless Headphones")
.font_size(16.0)
.font_weight(FontWeight::Bold)
)
.child(
// Rating
rect()
.direction(Direction::Horizontal)
.gap(4.0)
.child(
svg()
.svg_data(star_svg)
.width(Size::px(16.0))
.height(Size::px(16.0))
.color(Color::from_rgb(255, 193, 7))
)
.child(
label()
.text("4.8 (120 reviews)")
.font_size(12.0)
.color(Color::GRAY)
)
)
.child(
label()
.text("$79.99")
.font_size(20.0)
.font_weight(FontWeight::Bold)
.color(Color::from_rgb(0, 120, 50))
)
)
}
Element Composition Patterns
Pattern 1: Stacking (Vertical)
Stack elements top to bottom:
rect()
.direction(Direction::Vertical)
.gap(16.0)
.child(element_1())
.child(element_2())
.child(element_3())
Pattern 2: Row (Horizontal)
Arrange elements left to right:
rect()
.direction(Direction::Horizontal)
.gap(8.0)
.child(element_a())
.child(element_b())
.child(element_c())
Pattern 3: Card Pattern
Outer container + content:
rect()
.background(...)
.corner_radius(...)
.padding(...)
.shadow(...)
.child(content())
Pattern 4: Overlay Pattern
Stack elements on top of each other:
rect()
.width(Size::px(200.0))
.height(Size::px(100.0))
.child(background_element())
.child(
rect()
.position(Position::Absolute)
.width(Size::fill())
.height(Size::fill())
.child(overlay_content())
)
Element Conversion
Freya provides a convenient shortcut: strings automatically convert to labels.
The Shortcut
// These are equivalent:
rect().child(label().text("Hello"))
rect().child("Hello") // String converts to label!
// Works with format strings too:
rect().child(format!("Count: {}", 5))
This makes your code cleaner and more readable:
// Before (verbose)
rect()
.child(label().text("Name:"))
.child(label().text(name))
// After (concise)
rect()
.child("Name:")
.child(name)
[!TIP] When to Use Explicit Labels Use the string shortcut for simple text. Use explicit
label()when you need styling:rect() .child("Plain text") // Shortcut .child(label().text("Bold").font_weight(FontWeight::Bold)) // Explicit
Common Mistakes
Forgetting Direction
Problem: Elements overlap unexpectedly.
rect()
// Missing .direction() - defaults to vertical, but you wanted horizontal?
.child(label().text("First"))
.child(label().text("Second"))
Solution: Always be explicit about direction:
rect()
.direction(Direction::Horizontal)
.child(label().text("First"))
.child(label().text("Second"))
Wrong Size Values
Problem: Element disappears or is too small.
rect()
.width(Size::auto()) // Might be 0 if no content!
.height(Size::auto())
Solution: Use fixed or fill sizes when appropriate:
rect()
.width(Size::px(200.0)) // Guaranteed size
.height(Size::px(100.0))
Text Truncation
Problem: Text gets cut off.
label()
.text("Very long text here...")
.width(Size::px(50.0)) // Too narrow!
Solution: Either:
- Make the container wider
- Use
paragraph()withmax_lines()and ellipsis
Summary
In this tutorial, you learned:
- rect() - Container element for layout and grouping
- label() - Simple, single-style text
- paragraph() - Rich text with mixed formatting using spans
- svg() - Vector graphics and icons
- image() - Raster images (PNG, JPEG)
- How to combine elements into real UI components
- Common patterns for composition
- Common mistakes to avoid
Previous: Part 1: Getting Started ←
Next: Part 3: Layout System →
In the next tutorial, we’ll dive deep into the layout system - understanding how to position elements, control spacing, and create responsive designs that work at any window size.