Widgets
All Available widgets
WidgetsBase.Button
— TypeButton(name; style=Styles(), dom_attributes...)
A simple button, which can be styled a style::Styles
. Set kwarg style=nothing
to turn off the default Bonito styling.
Example
App() do
style = Styles(
CSS("font-weight" => "500"),
CSS(":hover", "background-color" => "silver"),
CSS(":focus", "box-shadow" => "rgba(0, 0, 0, 0.5) 0px 0px 5px"),
)
button = Button("Click me"; style=style)
on(button.value) do click::Bool
@info "Button clicked!"
end
return button
end
WidgetsBase.TextField
— TypeTextField(default_text; style=Styles(), dom_attributes...)
A simple TextField, which can be styled via the style::Styles
attribute, style=nothing
turns off the default Bonito styling.
Example
App() do
style = Styles(
CSS("font-weight" => "500"),
CSS(":hover", "background-color" => "silver"),
CSS(":focus", "box-shadow" => "rgba(0, 0, 0, 0.5) 0px 0px 5px"),
)
textfield = TextField("write something"; style=style)
on(textfield.value) do text::String
@info text
end
return textfield
end
WidgetsBase.NumberInput
— TypeNumberInput(default_value; style=Styles(), dom_attributes...)
A simple NumberInput, which can be styled via the style::Styles
attribute, style=nothing
turns off the default Bonito styling.
Example
App() do
style = Styles(
CSS("font-weight" => "500"),
CSS(":hover", "background-color" => "silver"),
CSS(":focus", "box-shadow" => "rgba(0, 0, 0, 0.5) 0px 0px 5px"),
)
numberinput = NumberInput(0.0; style=style)
on(numberinput.value) do value::Float64
@info value
end
return numberinput
end
Bonito.Dropdown
— TypeDropdown(options; index=1, option_to_string=string, style=Styles(), dom_attributes...)
A simple Dropdown, which can be styled via the style::Styles
attribute, style=nothing
turns off the default Bonito styling.
Example
App() do
style = Styles(
CSS("font-weight" => "500"),
CSS(":hover", "background-color" => "silver"),
CSS(":focus", "box-shadow" => "rgba(0, 0, 0, 0.5) 0px 0px 5px"),
)
dropdown = Dropdown(["a", "b", "c"]; index=2, style=style)
on(dropdown.value) do value
@info value
end
return dropdown
end
Bonito.Card
— FunctionCard(
content;
style::Styles=Styles(),
backgroundcolor=RGBA(1, 1, 1, 0.2),
shadow_size="0 4px 8px",
padding="12px",
margin="2px",
shadow_color=RGBA(0, 0, 0.2, 0.2),
width="auto",
height="auto",
border_radius="10px",
div_attributes...,
)
A Card is a container with a shadow and rounded corners. It is a good way to group elements together and make them stand out from the background. One can easily style them via the above keyword arguments or via the style
argument with any CSS attribute.
Example
App() do
Card(
DOM.h1("This is a card");
width="200px",
height="200px",
backgroundcolor="white",
shadow_size="0 0 10px",
shadow_color="blue",
padding="20px",
margin="20px",
border_radius="20px",
style = Styles(
CSS("hover", "background-color" => "lightgray")
)
)
end
This is a card
Bonito.StylableSlider
— TypeStylableSlider(
range::AbstractVector;
value=first(range),
slider_height=15,
thumb_width=slider_height,
thumb_height=slider_height,
track_height=slider_height / 2,
track_active_height=track_height + 2,
backgroundcolor="transparent",
track_color="#eee",
track_active_color="#ddd",
thumb_color="#fff",
style::Styles=Styles(),
track_style::Styles=Styles(),
thumb_style::Styles=Styles(),
track_active_style::Styles=Styles(),
)
Creates a Stylable Slider, where the basic attributes are easily custimizable via keyword arguments, while the more advanced details can be styled via the style
, track_style
, thumb_style
and track_active_style
arguments with the whole might of CSS. This does not use <input type="range">
but is a custom implementation using <div>
s javascript, since it is not easily possible to style the native slider in a cross-browser way. For using pure HTML sliders, use Bonito.Slider
.
Example
App() do
Bonito.StylableSlider(
1:10;
value=5,
slider_height=20,
track_color="lightblue",
track_active_color="#F0F8FF",
thumb_color="#fff",
style=Styles(
CSS("hover", "background-color" => "lightgray"),
CSS("border-radius" => "0px"),
),
track_style=Styles(
"border-radius" => "3px",
"border" => "1px solid black",
),
thumb_style=Styles(
"border-radius" => "3px",
"border" => "1px solid black",
),
)
end
Bonito.Table
— TypeA simple wrapper for types that conform to the Tables.jl Table interface, which gets rendered nicely!
The Table
widget provides an interactive way to display tabular data that conforms to the Tables.jl interface. It supports custom styling, interactive sorting, and flexible cell rendering.
Basic Usage
# Create sample data using named tuples
data = [
(name="Alice", age=25, score=95.5),
(name="Bob", age=30, score=87.2),
(name="Charlie", age=22, score=92.8)
]
# Basic table
basic_table = Bonito.Table(data)
App(basic_table)
name | age | score |
---|---|---|
Alice | 25 | 95.5 |
Bob | 30 | 87.2 |
Charlie | 22 | 92.8 |
Custom Cell Styling
You can provide custom class and style callbacks to control the appearance of individual cells:
# Color coding function based on values
function score_class_callback(table, row, col, val)
# Color the score column based on value
if col == 3 && isa(val, Number) # Score column
if val >= 90
return "cell-good"
elseif val >= 80
return "cell-neutral"
else
return "cell-bad"
end
end
return "cell-default"
end
# Style callback for additional formatting
function score_style_callback(table, row, col, val)
if col == 3 && isa(val, Number) # Score column
return "font-weight: bold;"
end
return ""
end
styled_table = Bonito.Table(data;
class_callback=score_class_callback,
style_callback=score_style_callback)
App(styled_table)
name | age | score |
---|---|---|
Alice | 25 | 95.5 |
Bob | 30 | 87.2 |
Charlie | 22 | 92.8 |
Sorting Options
Tables support interactive sorting by clicking on headers (column sorting) or first cells (row sorting):
# Table with only column sorting enabled
column_sort_table = Bonito.Table(data;
allow_row_sorting=false,
allow_column_sorting=true)
# Table with all sorting disabled
no_sort_table = Bonito.Table(data;
allow_row_sorting=false,
allow_column_sorting=false)
App() do
DOM.div(column_sort_table, no_sort_table)
end
name | age | score |
---|---|---|
Alice | 25 | 95.5 |
Bob | 30 | 87.2 |
Charlie | 22 | 92.8 |
name | age | score |
---|---|---|
Alice | 25 | 95.5 |
Bob | 30 | 87.2 |
Charlie | 22 | 92.8 |
Working with DataFrames
The Table widget works seamlessly with DataFrames and other Tables.jl-compatible structures:
using DataFrames
df = DataFrame(
Product = ["Laptop", "Mouse", "Keyboard", "Monitor"],
Price = [999.99, 29.99, 79.99, 299.99],
Stock = [15, 120, 45, 8],
Available = [true, true, false, true]
)
# Custom formatter for currency and boolean values
function product_class_callback(table, row, col, val)
if col == 2 # Price column
return val > 100 ? "cell-neutral" : "cell-good"
elseif col == 4 # Available column
return val ? "cell-good" : "cell-bad"
end
return "cell-default"
end
product_table = Bonito.Table(df; class_callback=product_class_callback)
App(product_table)
Product | Price | Stock | Available |
---|---|---|---|
Laptop | 999.99 | 15 | true |
Mouse | 29.99 | 120 | true |
Keyboard | 79.99 | 45 | false |
Monitor | 299.99 | 8 | true |
Advanced Example: Financial Data
# Financial data with multiple metrics
financial_data = [
(company="TechCorp", revenue=1.2e6, profit_margin=0.15, employees=150),
(company="DataInc", revenue=2.8e6, profit_margin=0.08, employees=300),
(company="CloudSys", revenue=0.9e6, profit_margin=0.22, employees=85),
(company="WebFlow", revenue=1.8e6, profit_margin=0.12, employees=220)
]
function financial_class_callback(table, row, col, val)
if col == 2 # Revenue column
return val > 1.5e6 ? "cell-good" : "cell-neutral"
elseif col == 3 # Profit margin column
if val > 0.15
return "cell-good"
elseif val > 0.10
return "cell-neutral"
else
return "cell-bad"
end
end
return "cell-default"
end
function financial_style_callback(table, row, col, val)
if col == 3 # Profit margin column
return "font-family: monospace;"
end
return ""
end
financial_table = Bonito.Table(financial_data;
class_callback=financial_class_callback,
style_callback=financial_style_callback,
class="financial-data")
App(financial_table)
company | revenue | profit_margin | employees |
---|---|---|---|
TechCorp | 1.2e6 | 0.15 | 150 |
DataInc | 2.8e6 | 0.08 | 300 |
CloudSys | 900000.0 | 0.22 | 85 |
WebFlow | 1.8e6 | 0.12 | 220 |
Widgets in Layouts
There are a few helpers to e.g. put a label next to a widget:
Bonito.Labeled
— FunctionLabeled(object, label; label_style=Styles(), attributes...)
A Labeled container with a simople layout to put a label next to an object.
App() do
label_style = Styles(
"color" => "white",
"padding" => "3px",
"font-size" => "1.5rem",
"text-shadow" => "0px 0px 10px black, 1px 1px 3px black")
slider = StylableSlider(1:10)
Card(Labeled(slider, slider.value; label_style=label_style, width="auto"); backgroundcolor="gray")
end
To create more complex layouts, one should use e.g. Grid
, and visit the Layouting tutorial.
App() do session
s = Bonito.StylableSlider(0:10;)
d = Dropdown(["a", "b", "c"])
ni = NumberInput(10.0)
ti = Bonito.TextField("helo")
button = Button("click")
clicks = Observable(0)
on(session, button.value) do bool
clicks[] = clicks[] + 1
end
return Card(Grid(
button, Bonito.Label(clicks),
s, Bonito.Label(s.value),
d, Bonito.Label(d.value),
ni, Bonito.Label(ni.value),
ti, Bonito.Label(ti.value);
columns="1fr min-content",
justify_content="begin",
align_items="center",
); width="300px",)
end
Editor
This editor works in pure Javascript, so feel free to try out editing the Javascript and clicking eval
to see how the output changes. In Bonito/examples/editor.jl
, you will find a version that works with Julia code, but that requires a running Julia server of course.
using Bonito, Observables
src = """
(() => {
const canvas = document.createElement("canvas");
const context = canvas.getContext('2d');
const width = 500
const height = 400
canvas.width = width;
canvas.height = height;
const gradient = context.createRadialGradient(200, 200, 0, 200, 200, 200);
gradient.addColorStop("0", "magenta");
gradient.addColorStop(".25", "blue");
gradient.addColorStop(".50", "green");
gradient.addColorStop(".75", "yellow");
gradient.addColorStop("1.0", "red");
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
return canvas;
})();
"""
App() do session::Session
editor = CodeEditor("javascript"; initial_source=src, width=800, height=300)
eval_button = Button("eval")
output = DOM.div(DOM.span())
Bonito.onjs(session, eval_button.value, js"""function (click){
const js_src = $(editor.onchange).value;
const result = new Function("return " + (js_src))()
let dom;
if (typeof result === 'object' && result.nodeName) {
dom = result
} else {
const span = document.createElement("span")
span.innerText = result;
dom = span
}
Bonito.update_or_replace($(output), dom, false);
return
}
""")
notify(eval_button.value)
return DOM.div(editor, eval_button, output)
end
MethodError: no method matching Styles(::Pair{String, String}, ::Pair{String, String}, ::Styles) The type `Styles` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: Styles(!Matched::Styles, ::Any...) @ Bonito ~/work/Bonito.jl/Bonito.jl/src/rendering/styling.jl:140 Styles(!Matched::Pair...) @ Bonito ~/work/Bonito.jl/Bonito.jl/src/rendering/styling.jl:139 Styles(!Matched::CSS, !Matched::Pair...) @ Bonito ~/work/Bonito.jl/Bonito.jl/src/rendering/styling.jl:136 ...
Stacktrace:
[1] CodeEditor(language::String; height::Int64, initial_source::String, theme::String, style::Styles, editor_options::@Kwargs{…})
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/widgets.jl:743
[2] (::Main.__atexample__named__1.var"#44#45")(session::Session{Bonito.SubConnection})
@ Main.__atexample__named__1 ./widgets.md:267
[3] (::Bonito.var"#19#20"{…})(session::Session{…}, request::HTTP.Messages.Request)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/types.jl:356
[4] rendered_dom(session::Session{Bonito.SubConnection}, app::App, target::HTTP.Messages.Request; apply_jsrender::Bool)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/app.jl:42
[5] rendered_dom (repeats 2 times)
@ ~/work/Bonito.jl/Bonito.jl/src/app.jl:38 [inlined]
[6] session_dom(session::Session{Bonito.SubConnection}, app::App; init::Bool, html_document::Bool)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/session.jl:362
[7] session_dom
@ ~/work/Bonito.jl/Bonito.jl/src/session.jl:361 [inlined]
[8] show_html(io::IOContext{IOBuffer}, app::App; parent::Session{NoConnection})
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/display.jl:70
[9] show_html(io::IOContext{IOBuffer}, app::App)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/display.jl:63
[10] show
@ ~/work/Bonito.jl/Bonito.jl/src/display.jl:97 [inlined]
[11] __binrepr
@ ./multimedia.jl:173 [inlined]
[12] _textrepr(m::MIME{Symbol("text/html")}, x::App, context::Pair{Symbol, Bool})
@ Base.Multimedia ./multimedia.jl:163
[13] #stringmime#9
@ /opt/hostedtoolcache/julia/1.12.0/x64/share/julia/stdlib/v1.12/Base64/src/Base64.jl:44 [inlined]
[14] display_dict(x::App; context::Pair{Symbol, Bool})
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/utilities/utilities.jl:576
[15] invokelatest
@ ./Base_compiler.jl:250 [inlined]
[16] runner(::Type{…}, node::MarkdownAST.Node{…}, page::Documenter.Page, doc::Documenter.Document)
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/expander_pipeline.jl:885
[17] dispatch(::Type{Documenter.Expanders.ExpanderPipeline}, ::MarkdownAST.Node{Nothing}, ::Vararg{Any})
@ Documenter.Selectors ~/.julia/packages/Documenter/eoWm2/src/utilities/Selectors.jl:170
[18] expand(doc::Documenter.Document)
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/expander_pipeline.jl:59
[19] runner(::Type{Documenter.Builder.ExpandTemplates}, doc::Documenter.Document)
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/builder_pipeline.jl:224
[20] dispatch(::Type{Documenter.Builder.DocumentPipeline}, x::Documenter.Document)
@ Documenter.Selectors ~/.julia/packages/Documenter/eoWm2/src/utilities/Selectors.jl:170
[21] #89
@ ~/.julia/packages/Documenter/eoWm2/src/makedocs.jl:280 [inlined]
[22] withenv(::Documenter.var"#89#90"{Documenter.Document}, ::Pair{String, Nothing}, ::Vararg{Pair{String, Nothing}})
@ Base ./env.jl:265
[23] #87
@ ~/.julia/packages/Documenter/eoWm2/src/makedocs.jl:279 [inlined]
[24] cd(f::Documenter.var"#87#88"{Documenter.Document}, dir::String)
@ Base.Filesystem ./file.jl:112
[25] makedocs(; debug::Bool, format::Documenter.HTMLWriter.HTML, kwargs::@Kwargs{…})
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/makedocs.jl:278
[26] top-level scope
@ ~/work/Bonito.jl/Bonito.jl/docs/make.jl:5
[27] include(mod::Module, _path::String)
@ Base ./Base.jl:306
[28] exec_options(opts::Base.JLOptions)
@ Base ./client.jl:317
[29] _start()
@ Base ./client.jl:550
Tailwinddashboard
Styles
is preferred to style components, but Bonito also includes some Tailwind based components. They're from before Styles
and will likely get removed in the future.
using Bonito
import Bonito.TailwindDashboard as D
function range_slider(orientation)
range_slider = RangeSlider(1:100; value=[10, 80])
range_slider.tooltips[] = true
range_slider.ticks[] = Dict(
"mode" => "range",
"density" => 3
)
range_slider.orientation[] = orientation
return range_slider
end
App() do
button = D.Button("click")
textfield = D.TextField("type in your text")
numberinput = D.NumberInput(0.0)
file_input = D.FileInput()
on(file_input.value) do file
@show file
end
slider = D.Slider("Test", 1:5)
checkbox = D.Checkbox("check this", true)
table = Bonito.Table([(a=22, b=33, c=44), (a=22, b=33, c=44)])
source = """
function test(a, b)
return a + b
end
"""
editor = CodeEditor("julia"; initial_source=source, width=250, height=200, scrollPastEnd=false)
dropdown = D.Dropdown("chose", ["option 1", "option 2", "option 3"])
vrange_slider = range_slider(Bonito.WidgetsBase.vertical)
hrange_slider = range_slider(Bonito.WidgetsBase.horizontal)
return DOM.div(
D.Card.([
D.FlexRow(
D.Card(D.FlexCol(
button,
textfield,
numberinput,
dropdown,
file_input,
slider,
checkbox,
class="items-start"
)),
D.Card(D.FlexCol(
D.Card(DOM.div(vrange_slider; style="height: 200px; padding: 1px 50px")),
D.Card(DOM.div(hrange_slider; style="width: 200px; padding: 50px 1px"),
)),
)),
D.FlexRow(
D.Card.([
D.Card(table; class="w-64")
editor
])
),
])...
)
end
MethodError: no method matching Styles(::Pair{String, String}, ::Pair{String, String}, ::Styles) The type `Styles` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: Styles(!Matched::Styles, ::Any...) @ Bonito ~/work/Bonito.jl/Bonito.jl/src/rendering/styling.jl:140 Styles(!Matched::Pair...) @ Bonito ~/work/Bonito.jl/Bonito.jl/src/rendering/styling.jl:139 Styles(!Matched::CSS, !Matched::Pair...) @ Bonito ~/work/Bonito.jl/Bonito.jl/src/rendering/styling.jl:136 ...
Stacktrace:
[1] CodeEditor(language::String; height::Int64, initial_source::String, theme::String, style::Styles, editor_options::@Kwargs{…})
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/widgets.jl:743
[2] (::Main.__atexample__named__1.var"#48#49")()
@ Main.__atexample__named__1 ./widgets.md:330
[3] (::Bonito.var"#23#24"{…})(session::Session{…}, request::HTTP.Messages.Request)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/types.jl:360
[4] rendered_dom(session::Session{Bonito.SubConnection}, app::App, target::HTTP.Messages.Request; apply_jsrender::Bool)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/app.jl:42
[5] rendered_dom (repeats 2 times)
@ ~/work/Bonito.jl/Bonito.jl/src/app.jl:38 [inlined]
[6] session_dom(session::Session{Bonito.SubConnection}, app::App; init::Bool, html_document::Bool)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/session.jl:362
[7] session_dom
@ ~/work/Bonito.jl/Bonito.jl/src/session.jl:361 [inlined]
[8] show_html(io::IOContext{IOBuffer}, app::App; parent::Session{NoConnection})
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/display.jl:70
[9] show_html(io::IOContext{IOBuffer}, app::App)
@ Bonito ~/work/Bonito.jl/Bonito.jl/src/display.jl:63
[10] show
@ ~/work/Bonito.jl/Bonito.jl/src/display.jl:97 [inlined]
[11] __binrepr
@ ./multimedia.jl:173 [inlined]
[12] _textrepr(m::MIME{Symbol("text/html")}, x::App, context::Pair{Symbol, Bool})
@ Base.Multimedia ./multimedia.jl:163
[13] #stringmime#9
@ /opt/hostedtoolcache/julia/1.12.0/x64/share/julia/stdlib/v1.12/Base64/src/Base64.jl:44 [inlined]
[14] display_dict(x::App; context::Pair{Symbol, Bool})
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/utilities/utilities.jl:576
[15] invokelatest
@ ./Base_compiler.jl:250 [inlined]
[16] runner(::Type{…}, node::MarkdownAST.Node{…}, page::Documenter.Page, doc::Documenter.Document)
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/expander_pipeline.jl:885
[17] dispatch(::Type{Documenter.Expanders.ExpanderPipeline}, ::MarkdownAST.Node{Nothing}, ::Vararg{Any})
@ Documenter.Selectors ~/.julia/packages/Documenter/eoWm2/src/utilities/Selectors.jl:170
[18] expand(doc::Documenter.Document)
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/expander_pipeline.jl:59
[19] runner(::Type{Documenter.Builder.ExpandTemplates}, doc::Documenter.Document)
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/builder_pipeline.jl:224
[20] dispatch(::Type{Documenter.Builder.DocumentPipeline}, x::Documenter.Document)
@ Documenter.Selectors ~/.julia/packages/Documenter/eoWm2/src/utilities/Selectors.jl:170
[21] #89
@ ~/.julia/packages/Documenter/eoWm2/src/makedocs.jl:280 [inlined]
[22] withenv(::Documenter.var"#89#90"{Documenter.Document}, ::Pair{String, Nothing}, ::Vararg{Pair{String, Nothing}})
@ Base ./env.jl:265
[23] #87
@ ~/.julia/packages/Documenter/eoWm2/src/makedocs.jl:279 [inlined]
[24] cd(f::Documenter.var"#87#88"{Documenter.Document}, dir::String)
@ Base.Filesystem ./file.jl:112
[25] makedocs(; debug::Bool, format::Documenter.HTMLWriter.HTML, kwargs::@Kwargs{…})
@ Documenter ~/.julia/packages/Documenter/eoWm2/src/makedocs.jl:278
[26] top-level scope
@ ~/work/Bonito.jl/Bonito.jl/docs/make.jl:5
[27] include(mod::Module, _path::String)
@ Base ./Base.jl:306
[28] exec_options(opts::Base.JLOptions)
@ Base ./client.jl:317
[29] _start()
@ Base ./client.jl:550