Mobile
Android UI Patterns
Beyond the basics of column, row, and
button, Zornux gives you a full widget toolkit, a theming
system, and layout controls that compile to native Jetpack Compose — no
XML, no Kotlin, no Gradle files to edit.
Theming
A theme block inside mobile app sets the
visual identity for every screen. Colors, typography, and shape are
inherited by all widgets automatically:
mobile app "TaskFlow"
theme
primary "#1B5E20"
secondary "#66BB6A"
background "#FAFAFA"
surface "#FFFFFF"
error "#B00020"
on_primary "#FFFFFF"
on_secondary "#000000"
font_family "Inter"
corner_radius 12
end
screen Main
column
text "TaskFlow" style heading
button "Get Started"
when tapped
go to Tasks
end
end
end
end
start with Main
| Property | Controls |
|---|---|
primary | Buttons, active states, links. |
secondary | Accent elements, FABs, toggles. |
background | The screen background behind surfaces. |
surface | Cards, dialogs, bottom sheets. |
error | Error text, validation borders. |
on_primary / on_secondary | Text and icons on top of the primary/secondary color. |
font_family | The typeface for all text widgets. |
corner_radius | Default corner rounding for buttons and cards (in dp). |
Every theme property has a Material Design 3 default. Declare only what you want to override — a two-line theme block is perfectly valid.
Dark mode
Add a dark sub-block to provide dark-mode colors. Android
switches automatically based on the system setting:
theme
primary "#1B5E20"
background "#FAFAFA"
surface "#FFFFFF"
dark
primary "#81C784"
background "#121212"
surface "#1E1E1E"
end
end
You do not toggle dark mode yourself. The generated Compose code reads the system theme and selects the right palette. If no dark block is declared, Material 3 derives dark colors automatically from the light palette.
Text styles
The text widget accepts a style modifier to
control its typographic role:
column
text "Welcome back" style heading
text "You have 3 new tasks" style subheading
text "Tap a task to begin working on it." style body
text "Last updated: 2 minutes ago" style caption
end
| Style | Compose equivalent | Typical use |
|---|---|---|
heading | headlineMedium | Page titles. |
subheading | titleMedium | Section headers, card titles. |
body (default) | bodyLarge | Paragraph text. |
caption | bodySmall | Timestamps, footnotes, labels. |
label | labelLarge | Button text, input labels. |
Images
Display an image from a URL or a local asset:
screen Profile receives userId
state avatar = ""
when screen opens
avatar = http.get("https://api.example.com/users/" + userId + "/avatar")
end
column
image avatar
width 120
height 120
corner_radius 60
end
text "User " + userId style heading
end
end
Images accept size and shape modifiers:
| Modifier | Effect |
|---|---|
width N | Fixed width in dp. |
height N | Fixed height in dp. |
corner_radius N | Rounded corners (set to half the dimension for a circle). |
fit cover / fit contain | How the image fills the bounds. |
Cards
A card wraps content in an elevated surface with rounded
corners:
screen TaskList
state tasks = ["Design login", "Build API", "Write tests"]
column
text "My Tasks" style heading
for each task in tasks
card
row
text task
button "Done"
when tapped
show task + " completed!"
end
end
end
end
end
end
end
Cards inherit the surface color from the theme and pick up
corner_radius automatically.
Layout modifiers
Both column and row accept modifiers that
control spacing, alignment, and padding:
screen Settings
column padding 24 spacing 16
text "Settings" style heading
row spacing 12 align center
text "Notifications"
switch notifications_enabled
end
row spacing 12 align center
text "Dark mode"
switch dark_mode
end
spacer
button "Log out" style destructive
when tapped
go to Login
end
end
end
end
| Modifier | Applies to | Effect |
|---|---|---|
padding N | column, row, card | Internal padding in dp. |
spacing N | column, row | Gap between children in dp. |
align center / start / end | row | Cross-axis alignment. |
scroll | column | Make the column vertically scrollable. |
The spacer widget pushes content apart — it fills all
available space along the parent's axis.
Advanced widgets
Beyond text, button, and input,
the widget toolkit includes:
| Widget | Syntax | Renders |
|---|---|---|
| Switch | switch stateName | A toggle bound to a boolean state. |
| Checkbox | checkbox stateName "Label" | A labeled checkbox. |
| Slider | slider stateName from 0 to 100 | A range slider. |
| Progress | progress / progress value | Indeterminate or determinate progress indicator. |
| Divider | divider | A horizontal line separator. |
| Spacer | spacer | Flexible empty space. |
| Icon | icon "name" | A Material icon by name. |
screen Preferences
state volume = 50
state wifi_enabled = true
state agree = false
column padding 24 spacing 16
text "Preferences" style heading
row spacing 12 align center
text "Volume"
slider volume from 0 to 100
end
row spacing 12 align center
icon "wifi"
text "Wi-Fi"
switch wifi_enabled
end
checkbox agree "I accept the terms and conditions"
if agree
button "Continue"
when tapped
go to Home
end
end
end
end
end
Button styles
Buttons support a style modifier for common visual roles:
column spacing 12
button "Save changes" style primary
when tapped
save()
end
end
button "Cancel" style outline
when tapped
go back
end
end
button "Delete account" style destructive
when tapped
show "Are you sure?"
end
end
end
| Style | Appearance |
|---|---|
primary (default) | Filled with the primary color. |
outline | Bordered, transparent background. |
text | No border, no background — just the label. |
destructive | Filled with the error color. |
Forms and validation
Build forms by combining input widgets with state and
conditional rendering. Validate before submission:
screen Register
state name = ""
state email = ""
state password = ""
state error_message = ""
column padding 24 spacing 16
text "Create Account" style heading
input name "Full name"
input email "Email address"
input password "Password" secure
if error_message is not ""
text error_message style caption
end
button "Sign Up"
when tapped
if name is "" or email is "" or password is ""
error_message = "All fields are required"
else if length of password is less than 8
error_message = "Password must be at least 8 characters"
else
error_message = ""
create result = http.post("https://api.example.com/register")
show "Account created!"
go to Login
end
end
end
end
end
Adding secure after the label turns an input into a password field — the characters are masked and the keyboard disables autocomplete.
Scrollable lists with sections
For long content, add the scroll modifier to a column.
Combine it with for each for data-driven lists:
screen Contacts
state contacts = []
when screen opens
contacts = http.get("https://api.example.com/contacts")
end
column scroll padding 16 spacing 8
text "Contacts" style heading
if length of contacts is 0
progress
text "Loading..." style caption
else
for each contact in contacts
card
row padding 16 spacing 12 align center
image contact.avatar
width 48
height 48
corner_radius 24
end
column spacing 4
text contact.name style subheading
text contact.email style caption
end
end
end
end
end
end
end
Accessibility
The generated Compose code inherits Android's accessibility framework.
Zornux adds a label modifier for widgets that need an
explicit content description:
button "X" label "Close dialog"
when tapped
go back
end
end
image avatar label "User profile photo"
width 64
height 64
corner_radius 32
end
icon "delete" label "Delete this item"
A text widget's content is already its accessible label. Adding a redundant label creates a double announcement — only add one when the visible text does not describe the purpose (like an icon-only button labeled "X").
What's next
- Android Architecture — state management, navigation patterns, multi-screen data flow, and offline-first design.
- Android Data & Storage — API consumption, local databases, caching, and sync.
- Mobile Capabilities — camera, location, notifications, biometrics, and native extensions.