Summon provides a rich set of built-in components for creating consistent user interfaces across platforms. This guide covers the core components and how to use them effectively.
The Text component is used to display text content.
// Using the standalone Summon implementation
@Composable
fun TextExample(): String {
return Text(
text = "Hello, World!",
modifier = Modifier()
.fontSize(16.px)
.fontWeight(FontWeight.Medium)
.color("#333333")
)
}
The Button component creates clickable buttons with various styles.
@Composable
fun ButtonExample(): String {
return Button(
text = "Submit",
modifier = Modifier()
.padding(8.px, 16.px)
.backgroundColor("#0077cc")
.color("#ffffff")
.borderRadius(4.px)
.cursor(Cursor.Pointer)
.onClick("handleSubmit()")
)
}
The TextField component creates input fields for text entry.
// Add TextField to your standalone implementation
@Composable
fun TextField(
value: String = "",
placeholder: String = "",
modifier: Modifier = Modifier()
): String {
val styleStr = if (modifier.styles.isNotEmpty()) " style=\"${modifier.toStyleString()}\"" else ""
val attrsStr = if (modifier.attributes.isNotEmpty()) " ${modifier.toAttributesString()}" else ""
val placeholderAttr = if (placeholder.isNotEmpty()) " placeholder=\"$placeholder\"" else ""
return "<input type=\"text\" value=\"$value\"$placeholderAttr$attrsStr$styleStr>"
}
@Composable
fun TextFieldExample(): String {
val text = remember { mutableStateOf("") }
return TextField(
value = text.value,
placeholder = "Enter your name",
modifier = Modifier()
.width(200.px)
.padding(8.px)
.border(1.px, BorderStyle.Solid, "#cccccc")
.borderRadius(4.px)
.attribute("oninput", "updateTextValue(this.value)")
)
}
The Checkbox component creates selectable checkboxes.
// Add Checkbox to your standalone implementation
@Composable
fun Checkbox(
checked: Boolean = false,
label: String = "",
modifier: Modifier = Modifier()
): String {
val styleStr = if (modifier.styles.isNotEmpty()) " style=\"${modifier.toStyleString()}\"" else ""
val attrsStr = if (modifier.attributes.isNotEmpty()) " ${modifier.toAttributesString()}" else ""
val checkedAttr = if (checked) " checked" else ""
return if (label.isNotEmpty()) {
"<label$styleStr><input type=\"checkbox\"$checkedAttr$attrsStr> $label</label>"
} else {
"<input type=\"checkbox\"$checkedAttr$attrsStr$styleStr>"
}
}
@Composable
fun CheckboxExample(): String {
val checked = remember { mutableStateOf(false) }
return Checkbox(
checked = checked.value,
label = "Accept terms and conditions",
modifier = Modifier()
.padding(8.px)
.attribute("onchange", "updateCheckbox(this.checked)")
)
}
Link renders accessible anchors that participate in Summon's routing + hydration lifecycle. It accepts custom child content (text, icons, layouts) and now exposes two hydration-friendly knobs:
navigationMode = LinkNavigationMode.Client – prevents the browser from following the raw hash while keeping the desired target in data-href for Summon's runtime to interpret (ideal for smooth-scrolling section jumps).fallbackText – optional server-rendered text for environments that can't hydrate (e.g., email clients). When left null, the raw href is no longer echoed as fallback content, so you won't see duplicated /path labels.Link(
href = "#pricing",
navigationMode = LinkNavigationMode.Client,
modifier = Modifier()
.color("#0077cc")
.textDecoration(TextDecoration.None)
) {
Text("See pricing")
}
Link(
href = "https://example.com/download",
target = "_blank",
fallbackText = "Download specs"
) {
Icon(Icons.Download)
Text("Download specs")
}
For advanced link styling with composable content rendered inside the anchor tag, the platform renderer supports renderEnhancedLink with content:
// Rich link with icon and styled text
renderer.renderEnhancedLink(
href = "/about",
target = "_blank",
title = "Learn more about us",
ariaLabel = "About page",
modifier = Modifier()
.padding(8.px)
.display(Display.Flex)
.alignItems(AlignItems.Center)
.gap(8.px)
) {
Text("Learn More")
Icon(name = "arrow-right")
}
This renders the composable content directly inside the <a> tag, enabling complex link designs with icons, badges, or any other components.
The Row component arranges its children horizontally.
@Composable
fun RowExample(): String {
return Row(
modifier = Modifier()
.padding(16.px)
.gap(8.px)
) {
Text("Item 1") + Text("Item 2") + Text("Item 3")
}
}
The Column component arranges its children vertically.
@Composable
fun ColumnExample(): String {
return Column(
modifier = Modifier()
.padding(16.px)
.gap(8.px)
) {
Text("Item 1") + Text("Item 2") + Text("Item 3")
}
}
Note: The Column component in the standalone implementation uses flexbox with flex-direction: column for vertical layout.
The Grid component creates a CSS grid layout.
Grid(
modifier = Modifier()
.gridTemplateColumns("1fr 1fr 1fr")
.gap(16.px)
) {
repeat(9) { index ->
Div(
modifier = Modifier()
.padding(16.px)
.backgroundColor("#f0f0f0")
.borderRadius(4.px)
) {
Text("Grid Item ${index + 1}")
}
}
}
The Card component creates a container with a shadow and border.
Card(
modifier = Modifier()
.padding(16.px)
.boxShadow("0 2px 4px rgba(0, 0, 0, 0.1)")
.borderRadius(8.px)
) {
Column(
modifier = Modifier()
.padding(16.px)
.gap(8.px)
) {
Text(
text = "Card Title",
modifier = Modifier()
.fontSize(18.px)
.fontWeight(FontWeight.Bold)
)
Text("Card content goes here")
Button(text = "Learn More")
}
}
The Alert component displays important messages to users.
Alert(
title = "Success",
message = "Your changes have been saved.",
type = AlertType.Success,
onClose = { /* Handle close */ }
)
The Badge component displays a small status indicator.
Badge(
text = "New",
modifier = Modifier()
.backgroundColor("#ff0000")
.color("#ffffff")
.padding(4.px, 8.px)
.borderRadius(12.px)
.fontSize(12.px)
)
The AccessibleElement component wraps content with accessibility attributes to improve screen reader support and overall accessibility.
AccessibleElement(
role = AccessibilityUtils.NodeRole.BUTTON,
label = "Close dialog",
relations = mapOf("controls" to "main-dialog"),
modifier = Modifier()
.padding(8.px)
.backgroundColor("#0077cc")
.color("#ffffff")
) {
Text("Close")
}
For better document structure and accessibility, Summon provides semantic HTML wrapper components:
Header(id = "site-header") {
Heading(level = 1) {
Text("Website Title")
}
Nav {
Row {
Link("Home", "/")
Link("About", "/about")
Link("Contact", "/contact")
}
}
}
Main {
Section(id = "intro") {
Heading(level = 2) {
Text("Introduction")
}
Text("Welcome to our website...")
}
Article {
Heading(level = 2) {
Text("Latest News")
}
Text("News content goes here...")
}
}
Footer {
Text("© 2023 My Company")
}
Creating custom components in Summon is straightforward using the @Composable annotation:
// Add Card component to your standalone implementation
@Composable
fun Card(
modifier: Modifier = Modifier(),
content: () -> String
): String {
val cardModifier = modifier
.backgroundColor("white")
.borderRadius(8.px)
.boxShadow("0 2px 4px rgba(0, 0, 0, 0.1)")
.padding(16.px)
return Div(cardModifier) { content() }
}
@Composable
fun CustomCard(
title: String,
content: String,
buttonText: String
): String {
return Card(
modifier = Modifier()
.padding(16.px)
.boxShadow("0 2px 4px rgba(0, 0, 0, 0.1)")
.borderRadius(8.px)
) {
Column(
modifier = Modifier()
.padding(16.px)
.gap(8.px)
) {
Text(
text = title,
modifier = Modifier()
.fontSize(18.px)
.fontWeight(FontWeight.Bold)
) + Text(content) + Button(
text = buttonText,
modifier = Modifier()
.backgroundColor("#0077cc")
.color("white")
.padding(8.px, 16.px)
.borderRadius(4.px)
.cursor(Cursor.Pointer)
.onClick("handleCardButton()")
)
}
}
}
// Usage
@Composable
fun MyPage(): String {
return CustomCard(
title = "Welcome",
content = "This is a custom component example.",
buttonText = "Learn More"
)
}
Components can be composed together to build more complex UIs:
@Composable
fun UserProfile(user: User) {
Column(
modifier = Modifier()
.padding(16.px)
.gap(16.px)
) {
Card {
Column(modifier = Modifier().padding(16.px).gap(8.px)) {
Text(
text = user.name,
modifier = Modifier().fontSize(24.px).fontWeight(FontWeight.Bold)
)
Text(user.email)
Text("Member since: ${user.joinDate}")
}
}
Card {
Column(modifier = Modifier().padding(16.px).gap(8.px)) {
Text(
text = "Recent Activity",
modifier = Modifier().fontSize(18.px).fontWeight(FontWeight.Bold)
)
user.activities.forEach { activity ->
Row(
modifier = Modifier()
.padding(8.px)
.borderBottom(1.px, BorderStyle.Solid, "#eeeeee")
) {
Text(activity.date)
Spacer(width = 16.px)
Text(activity.description)
}
}
}
}
}
}
The TextArea component creates multi-line text input fields.
var text by remember { mutableStateOf("") }
TextArea(
value = text,
onValueChange = { text = it },
placeholder = "Enter your message",
rows = 5,
modifier = Modifier()
.width(300.px)
.padding(8.px)
.border(1.px, BorderStyle.Solid, "#cccccc")
.borderRadius(4.px)
)
The RadioButton component creates radio buttons for single selection.
var selectedOption by remember { mutableStateOf("option1") }
Column(modifier = Modifier().gap(8.px)) {
RadioButton(
selected = selectedOption == "option1",
onClick = { selectedOption = "option1" },
label = "Option 1"
)
RadioButton(
selected = selectedOption == "option2",
onClick = { selectedOption = "option2" },
label = "Option 2"
)
}
The Switch component creates toggle switches.
var enabled by remember { mutableStateOf(false) }
Switch(
checked = enabled,
onCheckedChange = { enabled = it },
label = "Enable notifications",
modifier = Modifier().padding(8.px)
)
The Select component creates dropdown selection fields.
var selectedValue by remember { mutableStateOf("") }
Select(
value = selectedValue,
onValueChange = { selectedValue = it },
options = listOf(
SelectOption("small", "Small"),
SelectOption("medium", "Medium"),
SelectOption("large", "Large")
),
placeholder = "Choose size",
modifier = Modifier().width(200.px)
)
The Slider component creates value selection sliders.
var value by remember { mutableStateOf(50f) }
Slider(
value = value,
onValueChange = { value = it },
min = 0f,
max = 100f,
step = 1f,
modifier = Modifier().width(300.px)
)
The RangeSlider component creates dual-handle range selection sliders.
var range by remember { mutableStateOf(20f to 80f) }
RangeSlider(
value = range,
onValueChange = { range = it },
min = 0f,
max = 100f,
step = 1f,
modifier = Modifier().width(300.px)
)
The DatePicker component creates date selection fields.
var selectedDate by remember { mutableStateOf<LocalDate?>(null) }
DatePicker(
value = selectedDate,
onValueChange = { selectedDate = it },
placeholder = "Select date",
modifier = Modifier().width(200.px)
)
The TimePicker component creates time selection fields.
var selectedTime by remember { mutableStateOf<LocalTime?>(null) }
TimePicker(
value = selectedTime,
onValueChange = { selectedTime = it },
placeholder = "Select time",
modifier = Modifier().width(200.px)
)
The FileUpload component creates file upload areas with drag & drop support.
var selectedFiles by remember { mutableStateOf<List<FileInfo>>(emptyList()) }
FileUpload(
onFilesSelected = { files ->
selectedFiles = files
},
accept = "image/*",
multiple = true,
modifier = Modifier()
.width(400.px)
.height(200.px)
.border(2.px, BorderStyle.Dashed, "#cccccc")
.borderRadius(8.px)
) {
Column(
modifier = Modifier()
.fillMaxSize()
.justifyContent(JustifyContent.Center)
.alignItems(AlignItems.Center)
) {
Text("Drag & drop files here or click to browse")
if (selectedFiles.isNotEmpty()) {
Text("${selectedFiles.size} files selected")
}
}
}
The Box component creates a container with positioning capabilities.
Box(
modifier = Modifier()
.size(200.px)
.backgroundColor("#f0f0f0")
.position(Position.Relative)
) {
// Positioned child
Text(
"Top Right",
modifier = Modifier()
.position(Position.Absolute)
.top(8.px)
.right(8.px)
)
// Centered child
Text(
"Center",
modifier = Modifier()
.position(Position.Absolute)
.top(50.percent)
.left(50.percent)
.transform("translate(-50%, -50%)")
)
}
The Spacer component creates flexible spacing.
Row {
Text("Start")
Spacer(modifier = Modifier().width(16.px))
Text("Middle")
Spacer(modifier = Modifier().weight(1f)) // Flexible spacer
Text("End")
}
The Divider component creates visual separators.
Column(modifier = Modifier().gap(16.px)) {
Text("Section 1")
Divider(
modifier = Modifier()
.fillMaxWidth()
.height(1.px)
.backgroundColor("#cccccc")
)
Text("Section 2")
}
The AspectRatio component maintains aspect ratio for its content.
AspectRatio(
ratio = 16f / 9f,
modifier = Modifier()
.fillMaxWidth()
.backgroundColor("#000000")
) {
// Content will maintain 16:9 aspect ratio
Image(
src = "/video-thumbnail.jpg",
alt = "Video thumbnail"
)
}
The ExpansionPanel component creates collapsible panels.
var expanded by remember { mutableStateOf(false) }
ExpansionPanel(
expanded = expanded,
onExpandedChange = { expanded = it },
header = {
Row(
modifier = Modifier()
.fillMaxWidth()
.padding(16.px)
.justifyContent(JustifyContent.SpaceBetween)
) {
Text("Click to expand", modifier = Modifier().fontWeight(FontWeight.SemiBold))
Icon(if (expanded) "▼" else "▶")
}
}
) {
Column(modifier = Modifier().padding(16.px)) {
Text("Expanded content goes here")
Text("This content is only visible when expanded")
}
}
The LazyColumn and LazyRow components create virtualized lists for better performance with large datasets.
// LazyColumn for vertical scrolling
LazyColumn(
modifier = Modifier()
.fillMaxWidth()
.height(400.px)
) {
items(1000) { index ->
Card(
modifier = Modifier()
.fillMaxWidth()
.padding(8.px)
) {
Text("Item $index", modifier = Modifier().padding(16.px))
}
}
// You can also use different item types
item {
Text(
"Header",
modifier = Modifier()
.padding(16.px)
.fontSize(20.px)
.fontWeight(FontWeight.Bold)
)
}
items(users) { user ->
UserRow(user)
}
}
// LazyRow for horizontal scrolling
LazyRow(
modifier = Modifier()
.fillMaxWidth()
.height(200.px)
) {
items(images) { image ->
Image(
src = image.url,
alt = image.description,
modifier = Modifier()
.width(150.px)
.height(150.px)
.padding(8.px)
)
}
}
The Image component displays images with various options.
Image(
src = "/assets/logo.png",
alt = "Company logo",
modifier = Modifier()
.width(200.px)
.height(100.px)
.objectFit(ObjectFit.Cover)
.borderRadius(8.px)
)
The Icon component displays icons from various sources.
// Text-based icon
Icon(
content = "✓",
modifier = Modifier()
.size(24.px)
.color("#00cc00")
)
// Image-based icon
Icon(
src = "/icons/settings.svg",
alt = "Settings",
modifier = Modifier()
.size(24.px)
.color("#666666")
)
The Progress and ProgressBar components show progress indicators.
// Indeterminate progress
Progress(
modifier = Modifier().size(32.px)
)
// Determinate progress bar
var progress by remember { mutableStateOf(0.7f) }
ProgressBar(
progress = progress,
modifier = Modifier()
.fillMaxWidth()
.height(8.px)
.backgroundColor("#e0e0e0")
.borderRadius(4.px)
)
The Snackbar components show temporary notifications.
@Composable
fun MyApp() {
val snackbarHostState = remember { SnackbarHostState() }
Box(modifier = Modifier().fillMaxSize()) {
// Your app content
Column {
Button(
onClick = {
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = "Action completed successfully",
actionLabel = "Undo"
)
}
}
) {
Text("Show Snackbar")
}
}
// Snackbar host positioned at the bottom
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier()
.align(Alignment.BottomCenter)
.padding(16.px)
)
}
}
The Tooltip component shows hover tooltips.
Tooltip(
content = {
Text(
"This is a helpful tooltip",
modifier = Modifier()
.padding(8.px)
.backgroundColor("#333333")
.color("#ffffff")
.borderRadius(4.px)
)
}
) {
Button(onClick = { /* Handle click */ }) {
Text("Hover me")
}
}
The HamburgerMenu component creates a responsive hamburger menu with toggle functionality, perfect for mobile navigation.
var isOpen by remember { mutableStateOf(false) }
HamburgerMenu(
modifier = Modifier().position(Position.Relative),
menuContainerModifier = Modifier()
.position(Position.Absolute)
.style("top", "100%")
.style("left", "0")
.backgroundColor(Color("#0a1628"))
.borderRadius("8px")
.border("1px", BorderStyle.Solid, Color("#333"))
.zIndex(1000)
.style("min-width", "280px"),
isOpen = isOpen,
onToggle = { isOpen = !isOpen },
iconColor = "white"
) {
// Menu content
Column(modifier = Modifier().padding(16.px)) {
Link(href = "/home") { Text("Home") }
Link(href = "/about") { Text("About") }
Link(href = "/contact") { Text("Contact") }
}
}
The menuContainerModifier parameter allows native Summon styling of the dropdown without requiring GlobalStyle CSS.
The TabLayout component creates tab-based navigation.
var selectedTab by remember { mutableStateOf(0) }
Column {
TabLayout(
selectedIndex = selectedTab,
onTabSelected = { selectedTab = it },
tabs = listOf("Overview", "Details", "Reviews"),
modifier = Modifier()
.fillMaxWidth()
.borderBottom(1.px, BorderStyle.Solid, "#cccccc")
)
// Tab content
when (selectedTab) {
0 -> OverviewContent()
1 -> DetailsContent()
2 -> ReviewsContent()
}
}
The Form and FormField components help manage form state and validation.
@Composable
fun RegistrationForm() {
val formState = rememberFormState()
Form(
state = formState,
onSubmit = { values ->
// Handle form submission
println("Form submitted with: $values")
}
) {
FormField(
name = "username",
validator = RequiredValidator("Username is required")
) { field ->
TextField(
value = field.value,
onValueChange = field.onChange,
placeholder = "Username",
error = field.error,
modifier = Modifier().fillMaxWidth()
)
}
FormField(
name = "email",
validator = EmailValidator("Invalid email address")
) { field ->
TextField(
value = field.value,
onValueChange = field.onChange,
placeholder = "Email",
type = "email",
error = field.error,
modifier = Modifier().fillMaxWidth()
)
}
Button(
onClick = { formState.submit() },
enabled = formState.isValid,
modifier = Modifier().marginTop(16.px)
) {
Text("Register")
}
}
}
Summon provides platform-specific extensions for components to enable platform-specific functionality:
// JVM-specific extension for Button
Button(
text = "Click me",
onClick = { /* Handle click */ }
).withJvmAccessKey('C') // Adds access key for desktop applications
// JS-specific extension for TextField
TextField(
value = text,
onValueChange = { text = it }
).withJsAutoFocus() // Automatically focuses the field when rendered