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 CodeEditor component provides a syntax-highlighted code editing experience.
var code by remember { mutableStateOf("fun main() { println(\"Hello\") }") }
CodeEditor(
value = code,
onValueChange = { code = it },
language = "kotlin",
modifier = Modifier().height(300.px).border(1.px, BorderStyle.Solid, "#ccc")
)
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")
}
}
LazyColumn and LazyRow compose only the visible viewport plus bounded overscan. Browser renderers observe the real scroll container and measured row sizes, preserve the viewport anchor when variable-height rows change, and release off-screen composition groups and callbacks.
Use a stable key whenever rows can be inserted, removed, or reordered:
val state = remember {
LazyListState().apply {
setItemSize(40f) // expected extent before a row is measured
setOverscrollItems(10)
}
}
LazyColumn(
state = state,
modifier = Modifier().fillMaxWidth().height(600.px)
) {
// The count overload stores one interval; it does not allocate 100,000 lambdas.
items(
count = 100_000,
key = { index -> messages[index].opaqueId }
) { index ->
MessageRow(messages[index])
}
}
For paged or capability-gated data, implement LazyListDataProvider. itemAt is requested only for composed viewport and overscan indices; render every result state explicitly:
val provider = object : LazyListDataProvider<Message> {
override val itemCount: Int get() = messageCount
override fun key(index: Int): Any = opaqueMessageId(index)
override fun itemAt(index: Int): LazyListItemResult<Message> = cache.read(index)
}
LazyColumn(state = state) {
items(provider) { _, result ->
when (result) {
is LazyListItemResult.Data -> MessageRow(result.value)
LazyListItemResult.Loading -> Text("Loading")
LazyListItemResult.Empty -> Text("Empty")
LazyListItemResult.Locked -> Text("Locked")
LazyListItemResult.PermissionDenied -> Text("Permission denied")
is LazyListItemResult.Error -> Text("Unable to load")
}
}
}
LazyListState.ensureItemVisible supports keyboard focus movement without composing the full dataset. Rows expose list semantics and one-based positions. Call setExposeItemCountToAccessibility(false) when revealing the total row count would disclose private metadata. Stable keys are used internally and are never written to DOM attributes.
LazyRow uses the same scope, provider, state, key, measurement, and accessibility contracts on the horizontal axis.
The SplitPane component creates a resizable split view.
SplitPane(
orientation = "horizontal",
modifier = Modifier().height(400.px),
first = {
Box(modifier = Modifier().backgroundColor("#eee").fillMaxSize()) {
Text("Left Pane")
}
},
second = {
Box(modifier = Modifier().backgroundColor("#ddd").fillMaxSize()) {
Text("Right Pane")
}
}
)
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 Chart component renders interactive charts using Chart.js.
Chart(
type = "bar",
data = """{
"labels": ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
"datasets": [{
"label": "# of Votes",
"data": [12, 19, 3, 5, 2, 3],
"backgroundColor": [
"rgba(255, 99, 132, 0.2)",
"rgba(54, 162, 235, 0.2)",
"rgba(255, 206, 86, 0.2)",
"rgba(75, 192, 192, 0.2)",
"rgba(153, 102, 255, 0.2)",
"rgba(255, 159, 64, 0.2)"
],
"borderColor": [
"rgba(255, 99, 132, 1)",
"rgba(54, 162, 235, 1)",
"rgba(255, 206, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)",
"rgba(255, 159, 64, 1)"
],
"borderWidth": 1
}]
}""",
modifier = Modifier().width(400.px).height(300.px)
)
The RichMarkdown component renders Markdown content as HTML.
RichMarkdown(
markdown = """
# Hello World
This is **bold** and *italic* text.
- List item 1
- List item 2
[Link to Google](https://google.com)
""".trimIndent(),
modifier = Modifier().padding(16.px)
)
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
Type-safe video component with browser policy enforcement:
// Basic video with controls
Video(
src = "video.mp4",
controls = true,
modifier = Modifier().width("100%")
)
// Autoplay hero video (muted is auto-enforced)
Video(
src = "hero.mp4",
autoplay = true,
loop = true,
controls = false,
poster = "poster.jpg"
)
// Multiple sources for format fallback
Video(
sources = listOf(
VideoSource("video.webm", "video/webm"),
VideoSource("video.mp4", "video/mp4")
)
)
// Pause on scroll with responsive sizing
Box(modifier = Modifier().pauseOnScroll()) {
Video(
src = "content.mp4",
autoplay = true,
muted = true,
modifier = Modifier()
.responsiveMedia()
.aspectRatio(16, 9)
)
}
Type-safe audio component:
// Basic audio with controls
Audio(
src = "audio.mp3",
controls = true
)
// Background music
Audio(
src = "background.mp3",
autoplay = true,
loop = true,
controls = false
)
See Video API Reference and Audio API Reference for full documentation.