{"id":20279752,"url":"https://github.com/genieframework/stipple.jl","last_synced_at":"2026-06-11T13:00:37.209Z","repository":{"id":37436687,"uuid":"244731576","full_name":"GenieFramework/Stipple.jl","owner":"GenieFramework","description":"The reactive UI library for interactive data applications with pure Julia.","archived":false,"fork":false,"pushed_at":"2026-05-17T21:50:20.000Z","size":4342,"stargazers_count":355,"open_issues_count":37,"forks_count":28,"subscribers_count":7,"default_branch":"main","last_synced_at":"2026-05-17T22:50:30.375Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GenieFramework.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-03-03T20:09:58.000Z","updated_at":"2026-05-17T21:30:41.000Z","dependencies_parsed_at":"2023-09-26T16:40:15.785Z","dependency_job_id":"94c5de1f-9051-4946-a49b-9af5be3697bb","html_url":"https://github.com/GenieFramework/Stipple.jl","commit_stats":{"total_commits":481,"total_committers":17,"mean_commits":"28.294117647058822","dds":0.5862785862785862,"last_synced_commit":"932f0f23917548bc925d8a470fcee41b247efb2e"},"previous_names":[],"tags_count":227,"template":false,"template_full_name":null,"purl":"pkg:github/GenieFramework/Stipple.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GenieFramework%2FStipple.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GenieFramework%2FStipple.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GenieFramework%2FStipple.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GenieFramework%2FStipple.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GenieFramework","download_url":"https://codeload.github.com/GenieFramework/Stipple.jl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GenieFramework%2FStipple.jl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34199516,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T13:32:58.314Z","updated_at":"2026-06-11T13:00:37.184Z","avatar_url":"https://github.com/GenieFramework.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://genieframework.com/\"\u003e\n    \u003cimg\n      src=\"docs/content/img/stipple-logo+text.svg\"\n      alt=\"Genie Logo\"\n      height=\"64\"\n    /\u003e\n  \u003c/a\u003e\n  \u003cbr /\u003e\n  \u003cp\u003e\n    \u003ch3\u003e\n      \u003cb\u003e\n        Stipple.jl\n      \u003c/b\u003e\n    \u003c/h3\u003e\n  \u003c/p\u003e\n  \u003cp\u003e\n    \u003cul\u003e Reactive Data Apps in Pure Julia\n    \u003c/ul\u003e\n  \u003c/p\u003e\n\n  [![Docs](https://img.shields.io/badge/stipple-docs-yellowgreen)](https://www.genieframework.com/docs/) [![current status](https://img.shields.io/badge/julia%20support-v1.6%20and%20up-dark%20green)](https://github.com/GenieFramework/Stipple.jl/blob/9530ccd4313d7a4e3da2351eb621152047bc5cbd/Project.toml#L32) [![Website](https://img.shields.io/website?url=https%3A%2F%2Fgenieframework.com\u0026logo=genie)](https://www.genieframework.com/#stipple-section) [![Tests](https://img.shields.io/badge/build-passing-green)](https://github.com/GenieFramework/Stipple.jl/actions) [![Stipple Downloads](https://shields.io/endpoint?url=https://pkgs.genieframework.com/api/v1/badge/Stipple)](https://pkgs.genieframework.com?packages=Stipple) [![Tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2FGenieFramework%2FGenie.jl)](https://twitter.com/AppStipple)\n\n  \u003cp\u003e\n    \u003ca href=\"https://www.genieframework.com/\"\u003e\n      \u003cimg\n        src=\"https://github.com/GenieFramework/Genie.jl/raw/main/docs/content/img/genie.gif\"\n        alt=\"Screenshot\"\n        width=\"100%\"\n      /\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp style=\"font-family:verdana;font-size:60%;margin-bottom:4%\" align=\"center\"\u003e\n\u003cu\u003eJulia data dashboard powered by Stipple and Genie. \u003ca href=\"https://learn.genieframework.com/app-gallery\"\u003eApp gallery\u003c/a\u003e\u003c/u\u003e\n\u003c/p\u003e\n  \nPart of [Genie Framework](https://genieframework.com), Stipple is a reactive UI library for building interactive data applications in pure Julia.\nIt uses [Genie.jl](\"https://github.com/GenieFramework/Genie.jl) on the server side and [Vuejs](https://vuejs.org) on the client. Stipple uses a high performance architecture that automatically synchronizes the state two-way (server -\u003e client and client -\u003e server), sending only JSON data over the wire.\n\u003c/div\u003e\n\nBesides the low-code API provided by Stipple, you can also use [Genie Builder](https://learn.genieframework.com/docs/genie-builder/quick-start) to build your reactive UIs. Genie Builder is a plugin for VSCode to help you create interactive Genie apps much faster using its visual drag-and-drop editor.\n\n\nTo learn more about Stipple and Genie Framework, visit the [documentation](https://learn.genieframework.com/docs/guides), and the [app gallery](https://learn.genieframework.com/app-gallery).\n\n\nIf you need help with anything, you can find us on [Discord](https://discord.com/invite/9zyZbD6J7H).\n\n---\n\nThe Stipple ecosystem also includes:\n\n* [StippleUI.jl](https://github.com/GenieFramework/StippleUI.jl) - the UI library for `Stipple.jl`, providing access to 30+ reactive UI elements of the [Quasar Framework](https://quasar.dev), including forms, lists, tables, as well as layout.\n* [StipplePlotly.jl](https://github.com/GenieFramework/StipplePlotly.jl) - Plotting library for `Stipple.jl` based on [Plotly](https://plotly.com/javascript/)'s Graphing Library including event forwarding for interactive plots.\n* [StipplePlotlyExport.jl](https://github.com/GenieFramework/StipplePlotlyExport.jl) - add-on for `StipplePlotly.jl` to allow server side generation and exporting of plots. \n* [StippleLatex.jl](https://github.com/GenieFramework/StippleLatex.jl) - support for reactive Latex content based on the [Vue-Katex](https://github.com/lucpotage/vue-katex) plugin. \n\n### News (0.31.4 - v0.31.25):\n\n- Support GenieBuilder with Vue 3\n- Add `@fixtype` to prevent Revise from raising a world-age issue when revising app-mixins\n- Generate Vue Components from apps, see example below:\n\n### News (v0.31.3/0.31.4):\n### Features\n#### Synchronization\n- add functions `synchronize!(o1, o2)` and `unsynchronize!(o1, o2)` to sync/unsync reactive variables to external Observables. Sync is bidirectional by default\n- Example below.\n#### Finalizers\n- add finalizers that strip off listeners when a model goes out of scope.\n- custom finalizers can be added, e.g. to unsync model variables from external sources\n## News (v0.31):\n### Features\n- referring to variables in the `@app` declaration, e.g. `@in y = x + 1`\n- multiple models on one page\n- debugging of model events with `Stipple.debug()`\n- add `throttle` in analogy to `debounce`\n- `@handler` macro to define app handlers outside of `@app`\n- handler merging in mixins\n- accept Inf and NaN in floats\n- routehandlers `pre` / `post` for `@page` \n- precompilation by `@stipple_precompile`\n### Fixes / Improvements\n- reconnection after network interruption, e.g. energy save mode\n- `@async` calls in `@onchange` sections\n- remove state-changing evals from ReactiveTools macros\n\n## Vue 3 / Quasar 2\n\nFrom version 0.30 on Stipple has upgraded the front-end libraries to Vue3 / Quasar 2, as Vue-2 has reached its end-of-life.\nWe recommend to use at least v0.30.3, as we have fixed some relevant bugs.\n(Some of these bugs were also present in previous Vue2 versions.\nwe therefore recommend to use at least v0.29.1 for legacy Vue2 support.)\n\nWe have put lots of effort in making migration as easy as possible. Nevertheless, there are some places where advanced apps might need a little tweeking.\n\n## Main Changes for version \u003e= v0.30\n\nFirst of all, basic applications continue to function properly without any changes.\n\nHowever, if you have developed applications with external plugins or components you might need to do some adaptations.\nWe have introduced a vue2compat layer to make the migration as easy as possible.\n\n### Components\n- Components can no longer be registered in Vue, but rather in the app instance. If you are using legacy components, e.g. by including Vue libraries, you can verify whether they have been recognized by entering `vueLegacy` in the browser console once the page has been loaded (right-click -\u003e 'Inspect'). Under the field \"components\" you will find the components to be registered. Registering is then done by adding `Stipple.register_global_component(\"\u003ccomponent name\u003e\", legacy = true)` to your app.\n- Vue-2 components often use `this.$set(this.field, key, value)` to set object fields, which is incompatible with Vue-3 syntax. Use a normal assignment instead, e.g. `field[key] = value`\n\n### Plugins\nIf libraries install plugins, they need to be added to the app via `Stipple.add_plugin(MyApp, \"\u003cplugin name\u003e\")`.\nIf you are using explicit models replace 'MyApp' by the model type, otherwise use the module's name.\n\n### Component Attributes\nVue-2 syntax for synchronizing additional fields other than the v-model has changed from `\u003cfieldname\u003e.sync` to `v-model:\u003cfieldname\u003e`, e.g. from `:pagination.sync = \"myvar` to `v-model:pagination = \"myvar\"`. In StippleUI we had offered \"paginationsync = :myvar\" which will be automatically translated to the new syntax. If want to explicitly enter the original form, you can do so by using the var\"\" syntax, \" , e.g. `table(:mytable, var\"v-model:pagination\" = \"test\")`\n\n### Templates\nIn Vue-3 `v-if` and `v-for` precedence have change. So if you use both attributes in one component make sure to split your component in a container with a `v-for` attribute and a component, e.g. `div` or `span` with a `v-if` attribute.\nIn StippleUI syntax `v-if` corresponds to `@if(\"...\")`and `v-for`  corresponds to `@for(\"...\")`\n\n### More Migration Support \n... can be found at the Quasar site: https://quasar.dev/start/upgrade-guide/\nand at the Vue site: https://v3-migration.vuejs.org/\n\n## Installation\n\nStipple can be added from the GitHub repo, via `Pkg`:\n```julia\npkg\u003e add Stipple\n```\n\n## Example 1\n\nThis snippet below illustrates the structure of a reactive UI built with Stipple. See its [documentation page](https://learn.genieframework.com/docs/reference/reactive-ui/introduction) for a detailed explanation.\n\n```julia\nmodule App\n#setup of the Genie Framework environment\nusing GenieFramework\n@genietools\n\n# reactive code\n@app begin\n    # reactive variables synchronised between server and browser\n    @in N = 0\n    @out msg = \"\"\n    #reactive handler, executes code when N changes\n    @onchange N begin\n        msg = \"N = $N\"\n    end\nend\n\n# UI components\nfunction ui()\n    [\n        cell([\n                p(\"Enter a number\")\n                # variables are bound to a component using their symbol name\n                textfield(\"N\", :N )\n            ])\n        cell([\n                bignumber(\"The value of N is\", :N)\n            ])\n    ]\nend\n\n# definition of root route\n@page(\"/\", ui)\nend\n```\n\n\u003cimg src=\"https://learn.genieframework.com/assets/docs/ui/enternumber.png\" width=\"200px\" style=\"margin-left:auto;margin-right:auto\"\u003e \u003c/img\u003e\n\n## Example 2 (Synchronization to external sources)\n\n```julia\nusing Stipple, Stipple.ReactiveTools\nusing StippleUI\n\nusing GenieSession\n\nconst XX = Dict{String, Reactive}()\n\n@app Observer begin\n    @in x = 0\n    @private session = \"\"\n\n    @onchange isready begin\n        @info \"session: $session\"\n        println(\"hi\")\n        r = get!(XX, session, R(x))\n        synchronize!(__model__.x, r)\n    end\nend\n\n@page(\"/\", slider(1:100, :x), model = Observer, post = model -\u003e begin model.session[] = session().id *\"\"; nothing end)\n\n@event Observer :finalize begin\n    println(\"unsynchronizing ...\")\n    @info unsynchronize!(__model__.x)\n    notify(__model__, Val(:finalize))\nend\n\n@debounce Observer x 0\n@throttle Observer x 10\n\nup()\n```\n\n## Example 3 (Generate a Vue Component for an animated Copy Button)\n\n```julia\nusing Stipple, Stipple.ReactiveTools\nusing StippleUI\nimport Stipple.opts\n\n# defining a simple copy button component\n@app VueCopyButton begin\n    @out copied = false\nend\n\n@methods VueCopyButton [\n    :handleCopy =\u003e js\"\"\"function () {\n        const text = typeof this.text === 'function' ? this.text() : this.text;\n        Quasar.copyToClipboard(text).then(() =\u003e {\n          this.copied = true;\n          setTimeout(() =\u003e {\n            this.copied = false;\n          }, 700);\n        });\n      }\n    \"\"\"\n]\n\n@props VueCopyButton opts(\n    text = opts(\n        type = [js\"String\", js\"Function\"],\n        required = true\n    )\n)\n\n@template VueCopyButton btn(\n    icon = R\"this.copied ? 'check' : 'content_copy'\",\n    aria__label = R\"this.copied ? 'Copied!' : 'Copy to clipboard'\",\n    flat = true, size = \"sm\",\n    @click(\"this.handleCopy\")\n)\n\ncopybutton(text, args...; kwargs...) = vue(:copy__button, args...; text, kwargs...)\n\n# defining a simple app with a copy button\n@app MyApp begin\n    @in text = \"Copy this text\"\nend\n\n# registering the component in the app\n@components MyApp VueCopyButton\n\nui() = row([\n    textfield(\"Text to be copied\", :text, style = \"max-width: 200px;\"),\n    copybutton(:text, class = \"q-mt-md\")\n])\n\n@page(\"/\", ui, model = MyApp)\nup(open_browser = true)\n```\n\n## More information\n\nWhile Stipple/StippleUI documentation is still evolving, you can find help and many small examples via docstrings of the functions.\n\n```julia\nhelp?\u003e btn\nsearch: btn Btn btngroup btndropdown q__btn q__btn__group q__btn__dropdown button Buttons onbutton SubString @onbutton bitstring\n\n  Stipple has a component called btn which is a button with a few extra useful features. For instance, it comes in two shapes:\n  rectangle (default) and round. It also has the material ripple effect baked in (which can be disabled).\n\n  The button component also comes with a spinner or loading effect. You would use this for times when app execution may cause a      \n  delay and you want to give the user some feedback about that delay. When used, the button will display a spinning animation as     \n  soon as the user clicks the button.\n\n  When not disabled or spinning, btn emits a @click event, as soon as it is clicked or tapped.\n\n  Examples\n  ≡≡≡≡≡≡≡≡≡≡\n\n  julia\u003e btn(\"Move Left\", color = \"primary\", icon = \"mail\", @click(\"press_btn = true\"))\n\n  julia\u003e btn(\"Go to Hello World\", color = \"red\", type = \"a\", href = \"hello\", icon = \"map\", iconright = \"send\")\n\n  julia\u003e btn(\"Connect to server!\", color=\"green\", textcolor=\"black\", @click(\"btnConnect=!btnConnect\"), [\n            tooltip(contentclass=\"bg-indigo\", contentstyle=\"font-size: 16px\",\n            style=\"offset: 10px 10px\",  \"Ports bounded to sockets!\")]\n         )\n\n  ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────  \n\n  Arguments\n  ≡≡≡≡≡≡≡≡≡≡≡\n\n  ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────  \n\n    1. Behavior\n       • loading::Bool - Put button into loading state (displays a spinner – can be overridden by using a 'loading'\n       slot)\n       • percentage::Union{Int, Float64} - Percentage (0.0 \u003c x \u003c 100.0); To be used along 'loading' prop; Display a\n       progress bar on the background ex. 23\n       • darkpercentage::Bool - Progress bar on the background should have dark color; To be used along with\n       'percentage' and 'loading' props\n\n    2. Content\n       • label::Union{String, Int} - The text that will be shown on the button ex. Button Label\n       • icon::String - Icon name following Quasar convention; Make sure you have the icon library installed unless\n       you are using 'img:' prefix; If 'none' (String) is used as value then no icon is rendered (but screen real\n       estate will still be used for it) ex. map ion-add img:https://cdn.quasar.dev/logo/svg/quasar-logo.svg\n       img:path/to/some_image.png\n       • iconright::String - Icon name following Quasar convention; Make sure you have the icon library installed\n       unless you are using 'img:' prefix; If 'none' (String) is used as value then no icon is rendered (but screen\n       real estate will still be used for it) ex. map ion-add img:https://cdn.quasar.dev/logo/svg/quasar-logo.svg\n       img:path/to/some_image.png\n       • nocaps::Bool - Avoid turning label text into caps (which happens by default)\n       • nowrap::Bool - Avoid label text wrapping\n       • align::String - Label or content alignment default. center accepted values. left right center around between\n       evenly\n       • stack::Bool - Stack icon and label vertically instead of on same line (like it is by default)\n       • stretch::Bool - When used on flexbox parent, button will stretch to parent's height\n\n    3. General\n       • type::String - 1) Define the button native type attribute (submit, reset, button) or 2) render component with\n       \u003ca\u003e tag so you can access events even if disable or 3) Use 'href' prop and specify 'type' as a media tag\n       default. button ex. a submit reset button image/png href=\"https://some-site.net\" target=\"_blank\"\n       • tabindex::Union{Int, String} - Tabindex HTML attribute value\n\n    4. Navigation\n       • href::String - Native \u003ca\u003e link href attribute; Has priority over the 'to' and 'replace' props ex.\n       https://quasar.dev href=\"https://quasar.dev\" target=\"_blank\"\n       • target::String - Native \u003ca\u003e link target attribute; Use it only with 'to' or 'href' props ex. _blank _self\n       _parent _top\n\n    5. State\n       • loading::Bool - Put button into loading state (displays a spinner – can be overridden by using a 'loading'\n       slot)\n       • padding::String - Apply custom padding (vertical [horizontal]); Size in CSS units, including unit name or\n       standard size name (none|xs|sm|md|lg|xl); Also removes the min width and height when set\n       • color::String - Color name for component from the Color Palette (https://quasar.dev/style/color-palette) eg.\n       primary teal-10\n       • textcolor::String - Overrides text color (if needed); Color name from the Color Palette\n       (https://quasar.dev/style/color-palette) eg. primary teal-10\n       • dense::Bool - Dense mode; occupies less space\n       • round::Bool - Makes a circle shaped button\n```\n## Acknowledgements\n\nStipple builds upon the excellent [Vue.js Framework](https://quasar.dev/vue-components)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgenieframework%2Fstipple.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgenieframework%2Fstipple.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgenieframework%2Fstipple.jl/lists"}