Components
Components are Zylix’s reusable UI building blocks. They encapsulate structure, styling, and behavior into composable units that can be rendered to any platform.
Component Types
Zylix provides built-in component types for common UI elements:
pub const ComponentType = enum(u8) {
container = 0, // div-like container
text = 1, // text/span element
button = 2, // clickable button
input = 3, // text input field
image = 4, // image element
link = 5, // anchor link
list = 6, // ul/ol list
list_item = 7, // li item
heading = 8, // h1-h6
paragraph = 9, // p element
custom = 255, // custom component
};Component State
Each component tracks its interactive state:
pub const ComponentState = packed struct {
hover: bool = false, // Mouse is over component
focus: bool = false, // Component has focus
active: bool = false, // Pressed/clicked
disabled: bool = false, // Not interactive
checked: bool = false, // For checkbox/radio
expanded: bool = false, // For expandable components
loading: bool = false, // Loading indicator
error_state: bool = false, // Error indicator
};Component Properties
Common Properties
pub const ComponentProps = extern struct {
// Identity
id: u32 = 0, // Unique component ID
// Class name for styling
class_name: TextBuffer = std.mem.zeroes(TextBuffer),
class_name_len: u16 = 0,
// Text content
text: TextBuffer = std.mem.zeroes(TextBuffer),
text_len: u16 = 0,
// Style references
style_id: u32 = 0, // Default style
hover_style_id: u32 = 0, // On hover
focus_style_id: u32 = 0, // On focus
active_style_id: u32 = 0, // On active
disabled_style_id: u32 = 0, // When disabled
// Layout reference
layout_id: u32 = 0,
};Input Properties
pub const ComponentProps = extern struct {
// ... common props ...
// Input-specific
input_type: InputType = .text,
placeholder: TextBuffer = std.mem.zeroes(TextBuffer),
placeholder_len: u16 = 0,
value: TextBuffer = std.mem.zeroes(TextBuffer),
value_len: u16 = 0,
max_length: u16 = 0,
};
pub const InputType = enum(u8) {
text = 0,
password = 1,
email = 2,
number = 3,
search = 4,
tel = 5,
url = 6,
checkbox = 7,
radio = 8,
};Link Properties
pub const ComponentProps = extern struct {
// ... common props ...
// Link-specific
href: TextBuffer = std.mem.zeroes(TextBuffer),
href_len: u16 = 0,
target_blank: bool = false,
};Image Properties
pub const ComponentProps = extern struct {
// ... common props ...
// Image-specific
src: TextBuffer = std.mem.zeroes(TextBuffer),
src_len: u16 = 0,
alt: TextBuffer = std.mem.zeroes(TextBuffer),
alt_len: u16 = 0,
};Heading Properties
pub const ComponentProps = extern struct {
// ... common props ...
// Heading-specific
heading_level: HeadingLevel = .h1,
};
pub const HeadingLevel = enum(u8) {
h1 = 1, h2 = 2, h3 = 3,
h4 = 4, h5 = 5, h6 = 6,
};Event Handlers
Components can have multiple event handlers:
pub const EventHandler = struct {
event_type: EventType = .none,
callback_id: u32 = 0, // ID for callback lookup
prevent_default: bool = false,
stop_propagation: bool = false,
};
pub const MAX_EVENT_HANDLERS = 8;Event Types
pub const EventType = enum(u8) {
none = 0,
click = 1,
double_click = 2,
mouse_enter = 3,
mouse_leave = 4,
mouse_down = 5,
mouse_up = 6,
focus = 7,
blur = 8,
input = 9,
change = 10,
submit = 11,
key_down = 12,
key_up = 13,
key_press = 14,
};Component Tree
Components form a hierarchical tree structure:
pub const Component = struct {
id: u32 = 0,
component_type: ComponentType = .container,
props: ComponentProps = .{},
state: ComponentState = .{},
handlers: [MAX_EVENT_HANDLERS]EventHandler = undefined,
handler_count: u8 = 0,
// Tree structure
parent_id: u32 = 0,
children: [MAX_CHILDREN]u32 = undefined,
child_count: u16 = 0,
};Creating Components
Container
fn createContainer(tree: *VTree, class: []const u8) u32 {
var node = VNode.element(.div);
node.props.setClass(class);
return tree.create(node);
}
// Usage
const container = createContainer(tree, "main-container");Button
fn createButton(
tree: *VTree,
text: []const u8,
onclick_id: u32
) u32 {
var node = VNode.element(.button);
node.setText(text);
node.props.on_click = onclick_id;
node.props.setClass("btn");
return tree.create(node);
}
// Usage
const button = createButton(tree, "Click Me", CALLBACK_INCREMENT);Input Field
fn createInput(
tree: *VTree,
placeholder: []const u8,
oninput_id: u32
) u32 {
var node = VNode.element(.input);
node.props.setPlaceholder(placeholder);
node.props.on_input = oninput_id;
node.props.setClass("text-input");
return tree.create(node);
}
// Usage
const input = createInput(tree, "Enter task...", CALLBACK_TEXT_INPUT);List
fn createList(tree: *VTree, items: []const Item) u32 {
const list_id = tree.create(VNode.element(.ul));
for (items) |item| {
const item_id = createListItem(tree, item);
_ = tree.addChild(list_id, item_id);
}
return list_id;
}
fn createListItem(tree: *VTree, item: Item) u32 {
var node = VNode.element(.li);
// Set key for efficient updates
var key_buf: [32]u8 = undefined;
const key = std.fmt.bufPrint(&key_buf, "item-{d}", .{item.id}) catch "item";
node.setKey(key);
// Add text
const text_id = tree.create(VNode.textNode(item.text));
const item_id = tree.create(node);
_ = tree.addChild(item_id, text_id);
return item_id;
}Component Patterns
Compound Components
Build complex components from simpler ones:
fn createTodoItem(tree: *VTree, todo: Todo) u32 {
// Container
var container = VNode.element(.li);
container.setKey(todo.id_str);
if (todo.completed) {
container.props.setClass("todo-item completed");
} else {
container.props.setClass("todo-item");
}
const container_id = tree.create(container);
// Checkbox
var checkbox = VNode.element(.input);
checkbox.props.input_type = @intFromEnum(InputType.checkbox);
checkbox.props.on_change = CALLBACK_TOGGLE;
const checkbox_id = tree.create(checkbox);
// Text
const text_id = tree.create(VNode.textNode(todo.text));
// Delete button
var delete_btn = VNode.element(.button);
delete_btn.setText("×");
delete_btn.props.on_click = CALLBACK_DELETE;
delete_btn.props.setClass("delete-btn");
const delete_id = tree.create(delete_btn);
// Assemble
_ = tree.addChild(container_id, checkbox_id);
_ = tree.addChild(container_id, text_id);
_ = tree.addChild(container_id, delete_id);
return container_id;
}Conditional Rendering
fn createLoadingOrContent(tree: *VTree, loading: bool, content: []const u8) u32 {
if (loading) {
var spinner = VNode.element(.div);
spinner.props.setClass("spinner");
spinner.setText("Loading...");
return tree.create(spinner);
} else {
return tree.create(VNode.textNode(content));
}
}List Rendering
fn renderTodoList(tree: *VTree, todos: []const Todo, filter: Filter) u32 {
const list_id = tree.create(VNode.element(.ul));
for (todos) |todo| {
// Apply filter
const show = switch (filter) {
.all => true,
.active => !todo.completed,
.completed => todo.completed,
};
if (show) {
const item_id = createTodoItem(tree, todo);
_ = tree.addChild(list_id, item_id);
}
}
return list_id;
}Render Commands
Components generate render commands for platform execution:
pub const RenderCommandType = enum(u8) {
none = 0,
create_element = 1,
create_text = 2,
update_text = 3,
set_attribute = 4,
remove_attribute = 5,
add_class = 6,
remove_class = 7,
set_style = 8,
append_child = 9,
insert_before = 10,
remove_child = 11,
add_event_listener = 12,
remove_event_listener = 13,
set_property = 14,
};Platform Rendering
Each platform interprets components differently:
Web (JavaScript)
function applyCommand(cmd) {
switch (cmd.type) {
case 'create_element':
return document.createElement(cmd.tag);
case 'create_text':
return document.createTextNode(cmd.text);
case 'set_attribute':
element.setAttribute(cmd.name, cmd.value);
break;
case 'add_event_listener':
element.addEventListener(cmd.event, callback);
break;
}
}iOS (SwiftUI)
struct ZylixComponent: View {
let component: ComponentData
var body: some View {
switch component.type {
case .container:
VStack { renderChildren() }
case .button:
Button(component.text) { handleClick() }
case .input:
TextField(component.placeholder, text: $text)
case .text:
Text(component.text)
}
}
}Android (Compose)
@Composable
fun ZylixComponent(component: ComponentData) {
when (component.type) {
ComponentType.Container -> Column { renderChildren() }
ComponentType.Button -> Button(onClick = { handleClick() }) {
Text(component.text)
}
ComponentType.Input -> TextField(
value = text,
onValueChange = { handleInput(it) }
)
ComponentType.Text -> Text(component.text)
}
}Best Practices
1. Keep Components Focused
// Good: Single responsibility
fn createHeader(tree: *VTree, title: []const u8) u32 { ... }
fn createNavigation(tree: *VTree, items: []NavItem) u32 { ... }
fn createFooter(tree: *VTree, year: u32) u32 { ... }
// Avoid: Monolithic component
fn createEntirePage(tree: *VTree, everything: Everything) u32 { ... }2. Use Meaningful Keys
// Good: Stable, unique keys
node.setKey(item.uuid);
// Avoid: Index-based keys
node.setKey(std.fmt.bufPrint(&buf, "{d}", .{index}));3. Extract Reusable Patterns
// Good: Reusable button factory
fn createIconButton(
tree: *VTree,
icon: []const u8,
label: []const u8,
callback: u32
) u32 {
var btn = VNode.element(.button);
btn.props.setClass("icon-btn");
btn.props.on_click = callback;
// ... icon and label setup
return tree.create(btn);
}Next Steps
- Events - Handle component interactions
- State Management - Connect components to state
- Virtual DOM - Understand rendering internals