Widgets

All Available widgets

WidgetsBase.ButtonType
Button(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
source
WidgetsBase.TextFieldType
TextField(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
source
WidgetsBase.NumberInputType
NumberInput(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
source
Bonito.DropdownType
Dropdown(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
source
Bonito.CardFunction
Card(
    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
source

This is a card

Bonito.StylableSliderType
StylableSlider(
    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
source
Bonito.TableType

A simple wrapper for types that conform to the Tables.jl Table interface, which gets rendered nicely!

source

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.LabeledFunction
Labeled(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
source
1

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
0
0
a
10.0 helo

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