⚡Highly experimental library built on top of the Kobweb (Compose HTML framework). It allows you to use the official Bootstrap UI components with Kotlin and Jetpack Compose, to build a frontend on the web. You are required to use the kobweb framework, otherwise it won't work. At the moment, components are not yet fully customizable, but I'll work on it. ⚽ The goal is to release all bootstrap components, and only then work on it's customization furthermore. Silk UI layer which is included with Kobweb is not required for this library to work.
- Button
- Input
- Dropdown
- TextArea
- Checkbox
- RadioButton
- Switch
- Alert
- Toast
- Modal
- Select
- Range
- Progress
- Spinner
- Tooltip
- Collapse
- Carousel
- Breadcrumb
- Accordion
- NavBar
- Offcanvas
- Badge
- CloseButton
- ColorPicker
- FilePicker
- Pagination
Update a Project level build.gradle.kts
file:
repositories {
..
maven(url = "https://jitpack.io")
}
Update a site
module build.gradle.kts
file:
kobweb {
app {
index {
head.add {
script {
src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
}
link {
rel = "stylesheet"
href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css"
}
}
}
}
}
kotlin {
@Suppress("UNUSED_VARIABLE")
sourceSets {
..
val jsMain by getting {
dependencies {
..
implementation("com.github.stevdza-san:KotlinBootstrap:0.0.3")
}
}
}
}
A simple button usage:
BSButton(
text = "Sign in",
onClick = {}
)
You can update your button state to loading, as well as specify the exact Loading Text (optional):
var buttonLoading by remember { mutableStateOf(false) }
BSButton(
text = "Sign in",
loading = buttonLoading,
loadingText = "Please wait...",
onClick = { buttonLoading = true }
)
Add a Badge to your button:
BSButton(
text = "Shopping Cart",
badge = ButtonBadge(
text = "10"
),
onClick = {}
)
A simple usage with a placeholder:
var inputValue by remember { mutableStateOf("") }
BSInput(
value = inputValue,
placeholder = "Type here",
onValueChange = {
inputValue = it
}
)
Floating style input field, where a label is animated:
BSInput(
value = inputValue,
label = "Email Address",
floating = true,
onValueChange = {}
)
Positive validation style input field:
BSInput(
value = inputValue,
label = "Email Address",
placeholder = "Type here",
validation = InputValidation(
isValid = true
),
onValueChange = {}
)
Negative validation style input field:
BSInput(
value = inputValue,
label = "Email Address",
placeholder = "Type here",
validation = InputValidation(
isInvalid = true
),
onValueChange = {}
)
Disabled input field:
BSInput(
value = inputValue,
label = "Email Address",
placeholder = "Type here",
disabled = true,
onValueChange = {}
)
Plain text input field:
BSInput(
value = inputValue,
label = "Email Address",
placeholder = "Type here",
plainText = true,
onValueChange = {}
)
Dropdown with a placeholder:
BSDropdown(
placeholder = "Select a Platform",
items = listOf("Android", "iOS", "Web"),
onItemSelect = { index, value -> }
)
Dropdown with a dark background:
BSDropdown(
items = listOf("Android", "iOS", "Web"),
darkBackground = true,
onItemSelect = { index, value -> }
)
Disabled Dropdown item:
BSDropdown(
items = listOf("Android", "iOS", "Web"),
disabledItems = listOf("iOS"),
onItemSelect = { index, value -> }
)
Basic TextArea example with a label:
var value by remember { mutableStateOf("") }
BSTextArea(
value = value,
label = "Email Address",
placeholder = "Type here...",
onValueChange = { value = it }
)
Floating TextArea:
var value by remember { mutableStateOf("") }
BSTextArea(
value = value,
label = "Email Address",
floating = true,
onValueChange = { value = it }
)
Basic Checkbox usage:
BSCheckbox(
label = "Kotlin",
onClick = {}
)
Reversed order checkbox:
BSCheckbox(
label = "C++",
reverse = true,
onClick = {}
)
Toggle button style checkbox:
BSCheckbox(
label = "Python",
toggleButton = true,
onClick = {}
)
Basic RadioButtonGroup usage:
BSRadioButtonGroup {
BSRadioButton(label = "Android", onClick = {})
BSRadioButton(label = "iOS", onClick = {})
BSRadioButton(label = "Web", onClick = {})
}
RadioButtonGroup in a horizontal orientation:
BSRadioButtonGroup(inline = true) {
BSRadioButton(label = "Android", onClick = {})
BSRadioButton(label = "iOS", onClick = {})
BSRadioButton(label = "Web", onClick = {})
}
ToggleButton style of a RadioButtonGroup:
BSRadioButtonGroup(toggleButton = true) {
BSRadioButton(label = "Android", onClick = {})
BSRadioButton(label = "iOS", onClick = {})
BSRadioButton(label = "Web", onClick = {})
}
Switch with a default checked state:
BSSwitch(
label = "Android",
defaultChecked = true,
onClick = {}
)
Disabled Switch with a default unchecked state:
BSSwitch(
label = "Android",
disabled = true,
onClick = {}
)
Primary Style Alert with an Info icon and a bold text:
BSAlert(
message = "Visit my YouTube Channel: Stevdza-San",
icon = AlertIcon.Info,
bold = "Stevdza-San"
)
Success Style Alert with a Checkmark icon and a link text:
BSAlert(
message = "You have successfully purchased a book!",
alertLink = Pair("book", "https://google.com"),
icon = AlertIcon.Checkmark,
style = AlertStyle.Success
)
Dismissable Alert:
BSAlert(
message = "Dismissable Alert.",
dismissible = true,
style = AlertStyle.Dark
)
Even though a Toast component is not yet fully customizable, from this preview above you can see that there are different variations and styles that you can apply to it. For triggering a Toast component, you do need to call a special function showToast(toastId)
and pass your toast id, in order to properly display it on the screen. Every Toast components needs to be wrapped inside the BSToastGroup
composable. Also there's a ToastPlacement
parameter available on BSToastGroup
that you can use to modify a toast placement.
BSToast
gets visible once you trigger a showToast()
function:
BSToastGroup {
BSToast(
id = "toast",
title = "Welcome",
body = "Browse our website for more interesting products!",
onCloseClick = {}
)
}
BSButton(
text = "Show Toast",
onClick = {
showToast("toast")
}
)
BSToastBasic
which is not automatically dismissable, because it has autoHide
parameter equal to false:
BSToastGroup {
BSToastBasic(
id = "toastBasic",
text = "Thank you for your feedback!",
style = ToastStyle.Dark,
autoHide = false,
closeButtonDark = false,
onCloseClick = {}
)
}
BSToastAction
which contains additional positive/negative buttons:
BSToastGroup {
BSToastAction(
id = "toastAction2",
text = "Are you sure you want to delete 24 items?",
positiveButtonText = "Yes",
positiveButtonVariant = ButtonVariant.Primary,
negativeButtonVariant = ButtonVariant.Danger,
negativeButtonText = "Cancel",
style = ToastStyle.Dark,
onPositiveButtonClick = {},
onNegativeButtonClick = {}
)
}
BSModal
component is not visible by default. If you want to show it on your page, then you need to call a modifier showModalOnClick()
on a BSButton
or any other clickable composable, and pass the ID of the modal itself. After you do that, just click the component that has that modifier, and your BSModal
will appear.
A basic Modal usage:
BSModal(
id = "contactModal",
title = "Contact us",
body = {
Column {
BSInput(
modifier = Modifier
.fillMaxWidth()
.margin(bottom = 14.px),
value = "",
label = "Email Address",
placeholder = "Type here...",
onValueChange = {}
)
BSTextArea(
modifier = Modifier.fillMaxWidth(),
value = "",
label = "Message",
placeholder = "Type here...",
onValueChange = {}
)
}
},
positiveButtonText = "Send Message",
negativeButtonText = "Close",
onPositiveButtonClick = {},
onNegativeButtonClick = {}
)
BSButton(
modifier = Modifier.showModalOnClick(id = "contactModal"),
text = "Trigger",
onClick = {}
)
Select component's basic usage:
BSSelect(
items = listOf("Android", "iOS", "Web", "Desktop"),
placeholder = "Choose a Platform",
onItemSelected = { index, value -> }
)
Floating style of a Select component:
BSSelect(
items = listOf("Android", "iOS", "Web", "Desktop"),
placeholder = "Choose a Platform",
floating = true,
onItemSelected = { index, value -> }
)
Range's basic usage:
BSRange(
modifier = Modifier.width(300.px),
label = "Range (0-10)",
min = 0,
max = 10,
onSelect = {}
)
Basic Progress component usage:
BSProgress(percentage = 85.percent)
Stripped style:
BSProgress(
percentage = 85.percent,
striped = true
)
Animated Stripped style:
BSProgress(
percentage = 85.percent,
stripedAnimated = true
)
Default Spinner style:
BSSpinner(variant = SpinnerVariant.Default)
Grow Spinner style:
BSSpinner(variant = SpinnerVariant.DefaultGrow)
Before you can use and display a Tooltip, you need to initialize them by calling initializeTooltips()
function:
LaunchedEffect(Unit) {
initializeTooltips()
}
Usually, the content on top of which you want to add a tooltip, is specified as a content lambda of the BSTooltip
composable:
BSTooltip(
text = "https://stevdza-san.com",
content = {
A(href = "https://stevdza-san.com") {
SpanText(text = "Online Courses")
}
}
)
You can also change a direction of the tooltip, by using TooltipDirection
parameter:
BSTooltip(
text = "https://stevdza-san.com",
direction = TooltipDirection.Right,
content = {
A(href = "https://stevdza-san.com") {
SpanText(text = "Online Courses")
}
}
)
To make your button or any other clickable component as the one that triggers the BSCollapse
, you need to add a .showCollapse(id)
modifier and pass the BSCollapse
id.
Column(
modifier = Modifier.width(400.px),
horizontalAlignment = Alignment.CenterHorizontally
) {
BSButton(
modifier = Modifier
.alignContent(AlignContent.Center)
.showCollapse(id = "collapse1"),
text = "FAQ",
onClick = {}
)
BSCollapse(id = "collapse1") {
Column(modifier = Modifier.margin(top = 14.px)) {
SpanText(
modifier = Modifier
.fontSize(18.px)
.fontWeight(FontWeight.Bold),
text = "1. How long does the course take to complete?"
)
SpanText(
text = """
The course is self-paced, so you can complete it at your own speed.
On average, most students finish the course in about 3-6 weeks,
depending on the time they can dedicate to learning.
""".trimIndent()
)
}
}
}
A basic usage of Carousel component:
BSCarousel(
items = listOf(
CarouselItem(
image = "https://images.pexels.com/photos/2662116/pexels-photo-2662116.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
title = "Moraine Lake"
),
CarouselItem(
image = "https://images.pexels.com/photos/147411/italy-mountains-dawn-daybreak-147411.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
title = "Italy"
),
CarouselItem(
image = "https://images.pexels.com/photos/1166209/pexels-photo-1166209.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
title = "Lavender"
),
),
width = 900.px,
height = 500.px
)
You can specify and replace a default divider
parameter to change a separator string, and also you can set a currently selected BreadcrumbItem
as well.
BSBreadcrumb(
items = listOf(
BreadcrumbItem(
text = "Home",
href = "#"
),
BreadcrumbItem(
text = "Pricing",
href = "#"
),
BreadcrumbItem(
text = "Services",
href = "#"
),
BreadcrumbItem(
text = "About",
href = "#"
),
BreadcrumbItem(
text = "Contact us",
href = "#"
)
),
divider = ">",
currentItem = "About"
)
You can customize it's flush
parameter which will remove some borders and rounded corners to render accordions edge-to-edge with their parent container. alwaysOpen
parameter will make accordion items stay open when another item is opened.
Basic Accordion example:
BSAccordion(
modifier = Modifier.width(300.px),
items = listOf(
AccordionItem(
title = "Step 01: Identify your goals",
content = { SpanText(text = "Body text here...") },
defaultOpened = true
),
AccordionItem(
title = "Step 02: Write your goals",
content = { SpanText(text = "Body text here...")}
),
AccordionItem(
title = "Step 03: Analysis",
content = { SpanText(text = "Body text here...")}
),
AccordionItem(
title = "Step 04: Objectives",
content = { SpanText(text = "Body text here...")}
)
)
)
The NavBar typically appears at the top of the web page and contains various navigation elements such as links, buttons, dropdown menus, and branding elements like logos or site names. It adapts to different screen sizes and devices, making it ideal for responsive web design.
BSNavBar(
modifier = Modifier.fillMaxWidth(),
stickyTop = true,
itemsAlignment = Alignment.CenterHorizontally,
brand = NavBarBrand(
title = "KotlinBootstrap",
image = "https://getbootstrap.com/docs/5.3/assets/brand/bootstrap-logo.svg",
href = "#"
),
expand = NavBarExpand.LG,
backgroundStyle = BackgroundStyle.Dark,
items = listOf(
NavLink(
id = "homeLink",
title = "Home",
onClick = {
println("Index: $it Title: Home")
}
),
NavLink(
id = "servicesLink",
title = "Services",
onClick = {}
),
NavLink(
id = "pricingLink",
title = "Pricing",
onClick = {}
),
NavLink(
id = "aboutLink",
title = "About us",
onClick = {}
),
NavDropdown(
placeholder = "Language",
items = listOf(
NavDropdownItem(
id = "kotlinLanguage",
title = "Kotlin",
onClick = {
println("Index: $it Title: Kotlin")
}
),
NavDropdownItem(
id = "javaLanguage",
title = "Java",
onClick = {}
)
)
)
),
inputField = NavBarInputField(
placeholder = "Search",
value = "",
onValueChange = {}
),
button = NavBarButton(
text = "Search",
onClick = {}
)
)
You can add an extra parameter, to replace a default expandable menu with an Offcanvas
side bar:
BSNavBar(
..
offcanvas = NavBarOffcanvas(
id = "myOffcanvas",
title = "KotlinBootstrap",
dark = true
)
..
)
Offcanvas is used to create sidebar or panel that can slide in and out of the viewport. This component is often used to display additional content, navigation menus, or options without taking up the entire screen space.
val links = listOf("Home", "Pricing", "Services", "Contact us")
BSOffcanvas(
id = "myOffCanvas",
title = "Welcome!",
body = {
Column {
links.forEach { name ->
A(
attrs = Modifier
.margin(bottom = 16.px)
.textDecorationLine(TextDecorationLine.None)
.cursor(Cursor.Pointer)
.toAttrs()
) {
SpanText(name)
}
}
BSButton(
text = "Sign in",
onClick = {}
)
}
},
placement = OffcanvasPlacement.END
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
BSButton(
modifier = Modifier.showOffcanvasOnClick(id = "myOffCanvas"),
text = "Show",
onClick = {}
)
}
There are four different BadgeVariant
's: Straight, Regular, Rounded, Empty. You can customize the BackgroundStyle
of the badge, a fontFamily
, fontSize
, and fontWeight
as well.
Row(verticalAlignment = Alignment.CenterVertically) {
SpanText(
modifier = Modifier.margin(right = 8.px),
text = "Fitness Tracker"
)
BSBadge(
modifier = Modifier.margin(bottom = 8.px),
text = "New",
variant = BadgeVariant.Straight
)
}
A basic usage:
BSCloseButton()
A basic usage:
BSColorPicker(onColorSelected = {})
FilePicker component provides a required lambda onFileSelected
, that returns two strings. The first one represents a fileName, while the second one the actual file encoded in BASE_64 string.
A basic usage:
BSFileInput(
label = "Choose a file",
onFileSelected = { fileName, file -> }
)
BSPagination
component is used to divide long lists or tables into multiple pages, making it easier for users to navigate through the content.
A basic usage:
var currentPage by remember { mutableStateOf(1) }
BSPagination(
pages = 15,
maxVisiblePages = 3,
currentPage = currentPage,
previousButton = PreviousButton(
onClick = { currentPage = it }
),
nextButton = NextButton(
onClick = { currentPage = it }
),
onPageClick = { currentPage = it }
)