<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>R on Posit Open Source</title>
    <link>https://opensource.posit.co/languages/r/</link>
    <description>Recent content in R on Posit Open Source</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 29 Jun 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://opensource.posit.co/languages/r/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>watcher 0.2.0: filesystem watching for R, and the engine behind Shiny auto-reload</title>
      <link>https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/</link>
      <pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<p>If you&rsquo;ve ever saved a file in your Shiny app and watched the browser refresh on its own, you may have already used <a href="https://watcher.r-lib.org" target="_blank" rel="noopener">watcher</a> without knowing it.</p>
<p>watcher is a lightweight R package that watches files and directories for changes and reacts in the background. It shipped quietly last year as the engine behind Shiny&rsquo;s auto-reload, and until now that is mostly where it lived. watcher 0.2.0 is on CRAN, and we&rsquo;re taking this opportunity to introduce it as a general-purpose filesystem watcher for R developers to use.</p>
<p>Install it from CRAN:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;watcher&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="the-engine-behind-shiny-auto-reload">The engine behind Shiny auto-reload
</h2>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/editor-and-app.png"
      alt="A code editor showing an app.R file on the left and the running Shiny app on the right: an &ldquo;Old Faithful Eruptions&rdquo; histogram with a &ldquo;Number of bins&rdquo; slider." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>The inner loop of building a Shiny app is edit, save, switch to the browser, reload. Auto-reload removes the friction of the last two steps: you save, and the app reloads itself.</p>
<p>The easiest way to turn it on is Shiny&rsquo;s <a href="https://shiny.posit.co/r/reference/shiny/latest/devmode.html" target="_blank" rel="noopener">Developer Mode</a>, which flips on a handful of developer-friendly options for the session – auto-reload among them, alongside unminified JavaScript and full stack traces. Call <code>devmode()</code> once and run your app as usual:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">devmode</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">runApp</span><span class="p">(</span><span class="s">&#34;app.R&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Now every time you save a file in the app directory, the running app reloads to match:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/shiny-autoreload.gif"
      alt="A code editor and a running Shiny app side by side. As lines in app.R are edited and saved – the title, the bar colour, the number of bins – the app reloads to match, with no interaction in the browser." 
      loading="lazy"
    >
  </figure></div>
</p>
<p><em>Editing <code>app.R</code> on the left; the app on the right reloads on each save. No manual refresh or restart.</em></p>
<p>Under the hood, Developer Mode sets <code>shiny.autoreload = TRUE</code> (you can set that option directly if you&rsquo;d rather not switch on the rest of Developer Mode), which hands your app directory to watcher and starts it watching in the background. The instant you save a file, watcher&rsquo;s callback fires and Shiny pushes a reload to the browser.</p>
<p>Previously Shiny did this by polling: every few hundred milliseconds it re-listed the directory and compared modification times. That works, but the cost scales with the size of your project and the reload only ever happens on the next tick. watcher instead subscribes to the operating system&rsquo;s own filesystem-change notifications, so the reload fires on the save itself, and an idle app does no work at all.</p>
<p>For version 0.2.0, we&rsquo;ve simplified how the package installs from source. This means we can have it power auto-reload by default: the next release of Shiny will require watcher outright, so it&rsquo;s installed alongside Shiny with nothing to install by hand. (Shiny 1.14.0 uses watcher when it&rsquo;s present and falls back to polling otherwise – so for now it&rsquo;s worth installing watcher yourself).</p>
<h2 id="watcher-the-package">watcher, the package
</h2>
<p>This same machinery is available directly, outside of Shiny. <code>watcher()</code> returns an <a href="https://r6.r-lib.org" target="_blank" rel="noopener">R6</a> object that you can start and stop:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">watcher</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">dir.create</span><span class="p">(</span><span class="s">&#34;data&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">w</span> <span class="o">&lt;-</span> <span class="nf">watcher</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="s">&#34;data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">callback</span> <span class="o">=</span> <span class="nf">\</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span> <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;changed:&#34;</span><span class="p">,</span> <span class="n">paths</span><span class="p">,</span> <span class="s">&#34;\n&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">latency</span> <span class="o">=</span> <span class="m">0.5</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">w</span><span class="o">$</span><span class="nf">start</span><span class="p">()</span></span></span></code></pre></div></div>
<p>From now on, any change under <code>data/</code> calls your function back with the paths that changed:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">file.create</span><span class="p">(</span><span class="s">&#34;data/report.csv&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; changed: /home/you/project/data/report.csv</span></span></span></code></pre></div></div>
<p>Three arguments to <code>watcher()</code> cover most needs:</p>
<ul>
<li><strong><code>path</code></strong> – a file, a directory (watched recursively), or a vector of paths. Defaults to the working directory.</li>
<li><strong><code>callback</code></strong> – a function taking one argument: a character vector of the paths that changed. The default, <code>NULL</code>, simply writes the changed paths to <code>stdout</code>.</li>
<li><strong><code>latency</code></strong> – seconds to debounce events before reporting them. Defaults to 1.</li>
</ul>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">w</span><span class="o">$</span><span class="nf">stop</span><span class="p">()</span></span></span></code></pre></div></div>
<p>The returned object also has <code>$stop()</code>, <code>$is_running()</code> and <code>$get_path()</code> alongside <code>$start()</code>.</p>
<p>watcher runs on a background thread, but your callback runs on R&rsquo;s main thread, scheduled through <a href="https://later.r-lib.org" target="_blank" rel="noopener">later</a>. It fires when R is idle at the top level, whenever you call <code>later::run_now()</code>, or automatically inside an event loop such as Shiny&rsquo;s. This is what lets it slot easily into Shiny or plumber without blocking the session.</p>
<h2 id="put-it-to-work">Put it to work
</h2>
<p>watcher is deliberately small and general. Anything you want to happen when a file changes, you can wire to it:</p>
<ul>
<li>rebuild a report or re-render a document when its source or data changes</li>
<li>reprocess a directory as new files land in it</li>
<li>re-run tests or reload package code while you develop</li>
<li>reload configuration without restarting a long-running service</li>
</ul>
<p>The shape is always the same – give <code>watcher()</code> a path and a function:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">watcher</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rebuild</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">message</span><span class="p">(</span><span class="nf">format</span><span class="p">(</span><span class="nf">Sys.time</span><span class="p">()),</span> <span class="s">&#34;: &#34;</span><span class="p">,</span> <span class="nf">length</span><span class="p">(</span><span class="n">paths</span><span class="p">),</span> <span class="s">&#34; file(s) changed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ... your render / test / reload step here ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">w</span> <span class="o">&lt;-</span> <span class="nf">watcher</span><span class="p">(</span><span class="s">&#34;data&#34;</span><span class="p">,</span> <span class="n">rebuild</span><span class="p">,</span> <span class="n">latency</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">w</span><span class="o">$</span><span class="nf">start</span><span class="p">()</span></span></span></code></pre></div></div>
<p>The callback receives the paths that changed, so you can act on exactly what triggered it. File created, updated, removed and renamed events are monitored, for individual files or whole directory trees.</p>
<p>From here:</p>
<ul>
<li><a href="https://watcher.r-lib.org" target="_blank" rel="noopener">watcher package site</a> – reference and examples.</li>
<li><a href="https://github.com/r-lib/watcher" target="_blank" rel="noopener">r-lib/watcher</a> – source, issues, and feedback.</li>
<li>For Shiny, enable Developer Mode with <code>shiny::devmode()</code> (or set <code>options(shiny.autoreload = TRUE)</code> directly).</li>
</ul>
<h2 id="acknowledgments">Acknowledgments
</h2>
<p>watcher builds on <a href="https://github.com/emcrisostomo/fswatch" target="_blank" rel="noopener">libfswatch</a> by Enrico M. Crisostomo and Alan Dipert. This is a mature C++ filesystem-monitoring library, which uses the native, event-driven notification API on each platform.</p>
<p>Thanks go to Garrick Aden-Buie for wiring watcher into Shiny&rsquo;s auto-reload, and to everyone who has contributed issues, fixes, and feedback.</p>
<p>Questions and ideas are very welcome on GitHub: <a href="https://github.com/r-lib/watcher/issues" target="_blank" rel="noopener">r-lib/watcher</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/featured.jpg" length="298578" type="image/jpeg" />
    </item>
    <item>
      <title>Air 0.10.0</title>
      <link>https://opensource.posit.co/blog/2026-06-26_air-0-10-0/</link>
      <pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-26_air-0-10-0/</guid>
      <dc:creator>Davis Vaughan</dc:creator><description><![CDATA[<p>We&rsquo;re very excited to announce that Air 0.10.0 is out now!
<a href="https://github.com/posit-dev/air" target="_blank" rel="noopener">Air</a> is an extremely fast R code formatter, capable of styling entire projects in the blink of an eye.
We haven&rsquo;t done a release post in awhile, so this one will serve as a round up of everything from 0.8.2 to 0.10.0.</p>
<p>If you use <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>, the Air Extension has probably already updated you to the new release!
Otherwise, follow one of Air&rsquo;s <a href="https://posit-dev.github.io/air/editors.html" target="_blank" rel="noopener">editor guides</a> to install the latest version for your editor.</p>
<p>To see every bug fix, check out our <a href="https://github.com/posit-dev/air/blob/main/CHANGELOG.md" target="_blank" rel="noopener">CLI changelog</a> and <a href="https://github.com/posit-dev/air/blob/main/editors/code/CHANGELOG.md" target="_blank" rel="noopener">extension changelog</a>.</p>
<h2 id="assignment-style-">Assignment style 😎
</h2>
<p>The headlining feature of 0.10.0 is a new <code>assignment-style</code> option, one of our <a href="https://github.com/posit-dev/air/issues/359" target="_blank" rel="noopener">top requests</a>!
This allows you to enforce <code>&lt;-</code> or <code>=</code> for assignment throughout your codebase.
Choose one of <code>&quot;arrow&quot;</code> for <code>&lt;-</code>, <code>&quot;equal&quot;</code> for <code>=</code>, or <code>&quot;preserve&quot;</code> to leave existing code as is.
For example, with <code>assignment-style = &quot;arrow&quot;</code>:</p>
<!-- panache-ignore-format-start -->
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># This</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="n">fn</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span> <span class="o">&lt;-</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Is standardized to</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">&lt;-</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="n">fn</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span> <span class="o">&lt;-</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<!-- panache-ignore-format-end -->
<p>This option defaults to enforcing <code>&lt;-</code> everywhere, which is what the <a href="https://style.tidyverse.org/syntax.html#assignment-1" target="_blank" rel="noopener">style guide</a> already recommended.
Note that this is a breaking change for Air, as the previous behavior didn&rsquo;t enforce any assignment style.
To revert to the old behavior, use <code>&quot;preserve&quot;</code>.</p>
<p>This feature is surprisingly sophisticated!
In R, you can always convert from <code>=</code> to <code>&lt;-</code> without changing the meaning of the code, but the reverse isn&rsquo;t true.
Consider the following:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">quote</span><span class="p">(</span><span class="n">x</span> <span class="o">&lt;-</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This quotes the expression <code>x &lt;- 1</code> and returns it.
Blindly changing to <code>=</code> results in:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">quote</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<p>That&rsquo;s very different!
This sets an argument named <code>x</code> to <code>1</code>.
Trying to run this will result in an error since <code>quote()</code> doesn&rsquo;t have an <code>x</code> argument.
Air knows every case where <code>&lt;-</code> is allowed but <code>=</code> isn&rsquo;t, so you can rest assured that this option won&rsquo;t impact the meaning of your code in any way.</p>
<h2 id="positron-terminal-support">Positron terminal support
</h2>
<p>Positron&rsquo;s integration with Air has gotten even better!
When the Air extension activates, it now prepends the bundled Air executable to your integrated terminal&rsquo;s <code>PATH</code>.
This means:</p>
<ul>
<li>You can now run Air commands from Positron&rsquo;s terminal, even without installing the CLI separately.</li>
<li>Your terminal version of Air should now always match the version used by the editor itself.</li>
</ul>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/which-air.mp4"
        class="w-full h-full object-contain"
        title="Air in your Positron terminal"
        controls></video>
    </div>
<p>This is particularly useful with agents like Claude Code, where you&rsquo;ll often want to tell your agent to run Air on any R files it touches (see <code>usethis::use_tidy_agents()</code> in the development version of usethis for a way to automatically add this to an <code>AGENTS.md</code>).</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-claude.png"
      alt="Claude Code finding Air" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>If you&rsquo;ve already installed the Air CLI separately, note that the bundled version will take precedence in Positron&rsquo;s terminals by default.
To force both the editor and the integrated terminal to use your externally installed version of Air, set <code>air.executableStrategy: &quot;environment&quot;</code>.
To opt-out of terminal support, set <code>air.addExecutableToTerminalPath: false</code>.</p>
<h2 id="rstudio-improvements">RStudio improvements
</h2>
<p>Thanks to <a href="https://github.com/kevinushey" target="_blank" rel="noopener">Kevin Ushey</a>, RStudio&rsquo;s support for Air is improving rapidly!</p>
<blockquote>
<p>Note that you&rsquo;ll need RStudio 2026.06.0 for this, which isn&rsquo;t out yet, but should be by the end of next week.</p>
</blockquote>
<p>RStudio now has &ldquo;native&rdquo; support for Air, rather than just being configurable as an External Formatter.
If you opt-in to Air, RStudio will now automatically download the latest version of the <code>air</code> binary the first time you save a file, making the whole experience much smoother.</p>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-download.mp4"
        class="w-full h-full object-contain"
        title="Air auto downloading in RStudio"
        controls></video>
    </div>
<p>You can turn this on via <code>Tools -&gt; Global Options... -&gt; Code -&gt; Formatting</code>.
I recommend checking both <code>Use Air for code formatting</code> and <code>Reformat documents on save</code>.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-rstudio-settings.png"
      alt="RStudio settings for Air" 
      loading="lazy"
    >
  </figure></div>
</p>
<h2 id="lionstigersandbears-uv-pixi-and-mise-oh-my"><del>Lions, tigers, and bears</del> uv, pixi, and mise, oh my!
</h2>
<p>Air now lives on PyPi as <a href="https://pypi.org/project/air-formatter/" target="_blank" rel="noopener"><code>air-formatter</code></a> (sadly <code>air</code> was already taken), which means that you can now install Air&rsquo;s CLI via <a href="https://github.com/astral-sh/uv" target="_blank" rel="noopener">uv</a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install globally</span>
</span></span><span class="line"><span class="cl">uv tool install air-formatter
</span></span><span class="line"><span class="cl">air format path/to/file.R
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run one-off command</span>
</span></span><span class="line"><span class="cl">uvx --from air-formatter air format path/to/file.R</span></span></code></pre></div></div>
<p>It might sound a little absurd to put an R code formatting tool on Python&rsquo;s PyPI, but in a world where an increasingly large number of R users are also uv users, it&rsquo;s pretty darn convenient to be able to invoke it this way!</p>
<p>Additionally, Air is on <a href="https://github.com/conda-forge/air-feedstock" target="_blank" rel="noopener">conda-forge</a> thanks to <a href="https://github.com/salim-b" target="_blank" rel="noopener"><code>@salim-b</code></a>, which means you can also install it with <a href="https://pixi.prefix.dev/latest/" target="_blank" rel="noopener">pixi</a> and <a href="https://mise.jdx.dev/" target="_blank" rel="noopener">mise</a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Add to a project</span>
</span></span><span class="line"><span class="cl">pixi add air
</span></span><span class="line"><span class="cl">mise use conda:air
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install globally</span>
</span></span><span class="line"><span class="cl">pixi global install air
</span></span><span class="line"><span class="cl">mise use --global conda:air
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run one-off command</span>
</span></span><span class="line"><span class="cl">pixi <span class="nb">exec</span> air format path/to/my/script.R
</span></span><span class="line"><span class="cl">mise <span class="nb">exec</span> conda:air -- air format path/to/my/script.R</span></span></code></pre></div></div>
<h2 id="pre-commit-support">pre-commit support
</h2>
<p>Air now has <a href="https://pre-commit.com/" target="_blank" rel="noopener">pre-commit</a> and <a href="https://prek.j178.dev/" target="_blank" rel="noopener">prek</a> support via <a href="https://github.com/posit-dev/air-pre-commit" target="_blank" rel="noopener">posit-dev/air-pre-commit</a>.
To run Air on changed R files before every commit, add the following to your <code>.pre-commit-config.yaml</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">repos</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/posit-dev/air-pre-commit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Air version</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rev</span><span class="p">:</span><span class="w"> </span><span class="m">0.10.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Run the formatter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">air-format</span></span></span></code></pre></div></div>
<h2 id="stdin-support">stdin support
</h2>
<p>Air&rsquo;s CLI now has <a href="https://posit-dev.github.io/air/cli.html#stdin" target="_blank" rel="noopener">stdin support</a>!
You can activate this with the new <code>--stdin-file-path</code> option like so:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat R/across.R <span class="p">|</span> air format --stdin-file-path R/across.R</span></span></code></pre></div></div>
<p><code>air</code> will receive the contents of <code>cat R/across.R</code> on stdin, format it, and then emit the formatted result back out on stdout.</p>
<p>You typically won&rsquo;t call this directly, but it&rsquo;s very useful for editors and IDEs without full language server support.
For example, with <a href="https://zed.dev/" target="_blank" rel="noopener">Zed</a> you can configure Air as an external formatter with the following setup:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;formatter&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;external&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;air&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;arguments&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;format&#34;</span><span class="p">,</span> <span class="s2">&#34;--stdin-file-path&#34;</span><span class="p">,</span> <span class="s2">&#34;{buffer_path}&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>This allows Zed to invoke Air on every file save.</p>
<blockquote>
<p>Note that Zed does have full language server support, and it&rsquo;s recommended to use our <a href="https://posit-dev.github.io/air/editor-zed.html" target="_blank" rel="noopener">Air extension for Zed</a> instead, but it makes for a nice example anyways!</p>
</blockquote>
<p>Invoking Air via stdin can also be useful in some embedded contexts, such as inside <a href="https://github.com/eitsupi/arf" target="_blank" rel="noopener">arf</a>, an R console.</p>
<h2 id="shell-completions">Shell completions
</h2>
<p>Thanks to <a href="https://github.com/salim-b" target="_blank" rel="noopener"><code>@salim-b</code></a>, Air can now generate shell completions for itself via a new <code>air generate-shell-completion &lt;shell&gt;</code> command, where <code>&lt;shell&gt;</code> can be one of <code>zsh</code>, <code>bash</code>, <code>powershell</code>, <code>fish</code>, or <code>elvish</code>.
Read the <a href="https://posit-dev.github.io/air/cli.html#shell-completions" target="_blank" rel="noopener">documentation</a> for how to set this up for your specific shell.
With shell completions set up, you can <code>Tab</code> your way through <code>air</code> commands at the command line:</p>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-completions.mp4"
        class="w-full h-full object-contain"
        title="Air shell completions"
        controls></video>
    </div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/featured.jpg" length="638589" type="image/jpeg" />
    </item>
    <item>
      <title>CatBoost support in tidymodels</title>
      <link>https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/</guid>
      <dc:creator>Emil Hvitfeldt</dc:creator><description><![CDATA[<p><a href="https://catboost.ai/" target="_blank" rel="noopener">CatBoost</a> is a very popular and high-quality gradient boosting library.
With the latest releases of <a href="https://bonsai.tidymodels.org/" target="_blank" rel="noopener">bonsai</a> and <a href="https://parsnip.tidymodels.org/" target="_blank" rel="noopener">parsnip</a>,
you can now train CatBoost models from R using the same tidymodels interface you already use for xgboost, LightGBM, and the rest of the <code>boost_tree()</code> family.</p>
<h2 id="installing-catboost">Installing CatBoost
</h2>
<p>The one wrinkle is installation.
The CatBoost R package is not on CRAN,
so you can&rsquo;t reach for <code>install.packages(&quot;catboost&quot;)</code> directly.</p>
<p>Grab the URL for your platform from the <a href="https://catboost.ai/docs/en/installation/r-installation-binary-installation" target="_blank" rel="noopener">CatBoost R installation guide</a> and install it with the remotes package.
For example, on an Apple Silicon or Intel Mac:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;remotes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">remotes</span><span class="o">::</span><span class="nf">install_url</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;https://github.com/catboost/catboost/releases/download/v1.2.10/catboost-R-darwin-universal2-1.2.10.tgz&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">INSTALL_opts</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;--no-multiarch&#34;</span><span class="p">,</span> <span class="s">&#34;--no-test-load&#34;</span><span class="p">,</span> <span class="s">&#34;--no-staged-install&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>Swap in the release version, operating system, and architecture that match your setup.
The guide lists the full URL pattern and the binaries available for each release.</p>
<p>Once CatBoost itself is installed,
the tidymodels packages is just the usual packages:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># install.packages(&#34;pak&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;tidymodels&#34;</span><span class="p">,</span> <span class="s">&#34;bonsai&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
<p>You&rsquo;ll need <strong>bonsai 0.4.1</strong> (or later) and <strong>parsnip 1.4.0</strong> (or later),
which is where the CatBoost engine landed and got polished.</p>
<h2 id="fitting-a-catboost-model">Fitting a CatBoost model
</h2>
<p>CatBoost is supported as an engine for []<code>boost_tree()</code>](<a href="https://parsnip.tidymodels.org/reference/details_boost_tree_catboost.html%29" target="_blank" rel="noopener">https://parsnip.tidymodels.org/reference/details_boost_tree_catboost.html)</a>.
Loading bonsai registers the engine,
and from there it behaves like any other parsnip model spec:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidymodels</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bonsai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_spec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">boost_tree</span><span class="p">(</span><span class="n">trees</span> <span class="o">=</span> <span class="m">500</span><span class="p">,</span> <span class="n">learn_rate</span> <span class="o">=</span> <span class="m">0.05</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;catboost&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_mode</span><span class="p">(</span><span class="s">&#34;regression&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_fit</span> <span class="o">&lt;-</span> <span class="nf">fit</span><span class="p">(</span><span class="n">cat_spec</span><span class="p">,</span> <span class="n">mpg</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cat_fit</span></span></span></code></pre></div></div>
<pre><code>parsnip model object

CatBoost model (500 trees)
Loss function: RMSE
Fit to 10 feature(s)
</code></pre>
<h2 id="tuning">Tuning
</h2>
<p>The CatBoost engine supports the standard <code>boost_tree()</code> tuning parameters,
and the recent releases made tuning both faster and more correct.</p>
<p>A typical tuning setup looks like this:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">cat_spec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">boost_tree</span><span class="p">(</span><span class="n">trees</span> <span class="o">=</span> <span class="nf">tune</span><span class="p">(),</span> <span class="n">learn_rate</span> <span class="o">=</span> <span class="nf">tune</span><span class="p">())</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;catboost&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_mode</span><span class="p">(</span><span class="s">&#34;regression&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_wf</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">cat_spec</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">set.seed</span><span class="p">(</span><span class="m">123</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">folds</span> <span class="o">&lt;-</span> <span class="nf">vfold_cv</span><span class="p">(</span><span class="n">mtcars</span><span class="p">,</span> <span class="n">v</span> <span class="o">=</span> <span class="m">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tune_res</span> <span class="o">&lt;-</span> <span class="nf">tune_grid</span><span class="p">(</span><span class="n">cat_wf</span><span class="p">,</span> <span class="n">resamples</span> <span class="o">=</span> <span class="n">folds</span><span class="p">,</span> <span class="n">grid</span> <span class="o">=</span> <span class="m">20</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">show_best</span><span class="p">(</span><span class="n">tune_res</span><span class="p">,</span> <span class="n">metric</span> <span class="o">=</span> <span class="s">&#34;rmse&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 5 × 8
  trees learn_rate .metric .estimator  mean     n std_err .config         
  &lt;int&gt;      &lt;dbl&gt; &lt;chr&gt;   &lt;chr&gt;      &lt;dbl&gt; &lt;int&gt;   &lt;dbl&gt; &lt;chr&gt;           
1  1473     0.0379 rmse    standard    2.76     5   0.419 pre0_mod15_post0
2   527     0.0513 rmse    standard    2.81     5   0.443 pre0_mod06_post0
3  1053     0.0941 rmse    standard    2.83     5   0.447 pre0_mod11_post0
4   211     0.127  rmse    standard    2.84     5   0.400 pre0_mod03_post0
5   632     0.234  rmse    standard    2.86     5   0.480 pre0_mod07_post0
</code></pre>
<p>Thanks to the <a href="https://parsnip.tidymodels.org/articles/Submodels.html" target="_blank" rel="noopener">submodel trick</a>
tuning <code>trees</code> doesn&rsquo;t require refitting the model from scratch at every candidate value.</p>
<h2 id="orbital-support">orbital support
</h2>
<p>CatBoost models also work with <a href="https://orbital.tidymodels.org/" target="_blank" rel="noopener">orbital</a>,
allowing you to turn your fitted catboost model into SQL and run predictions directly inside a database.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">orbital</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_spec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">boost_tree</span><span class="p">(</span><span class="n">trees</span> <span class="o">=</span> <span class="m">50</span><span class="p">,</span> <span class="n">learn_rate</span> <span class="o">=</span> <span class="m">0.05</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;catboost&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_mode</span><span class="p">(</span><span class="s">&#34;regression&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_wf</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">cat_spec</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cat_fit</span> <span class="o">&lt;-</span> <span class="nf">fit</span><span class="p">(</span><span class="n">cat_wf</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">orbital_obj</span> <span class="o">&lt;-</span> <span class="nf">orbital</span><span class="p">(</span><span class="n">cat_fit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">orbital_obj</span></span></span></code></pre></div></div>
<pre><code>── orbital Object ──────────────────────────────────────────────────────────────
• .pred = dplyr::case_when(cyl &lt;= 5 ~ dplyr::case_when(wt &lt;= 2.17 ~ dplyr ...
────────────────────────────────────────────────────────────────────────────────
1 equations in total.
</code></pre>
<p>From here you can predict in-database with <code>predict()</code> against a database connection,
or generate the SQL directly with <code>orbital_sql()</code>.
See the <a href="https://orbital.tidymodels.org/" target="_blank" rel="noopener">orbital documentation</a> for the full set of supported backends.</p>
<h2 id="wrapping-up">Wrapping up
</h2>
<p>CatBoost is a great addition to the gradient boosting options available in tidymodels,
especially if you work with categorical features or want a strong out-of-the-box model.</p>
<p>For the full details, see the <a href="https://bonsai.tidymodels.org/news/index.html" target="_blank" rel="noopener">bonsai changelog</a> and the <a href="https://parsnip.tidymodels.org/news/index.html#parsnip-140" target="_blank" rel="noopener">parsnip 1.4.0 release notes</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/featured.png" length="296364" type="image/png" />
    </item>
    <item>
      <title>Investing in the Future of Interactive Computing: We&#39;ve Joined the Jupyter Foundation</title>
      <link>https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/</guid>
      <dc:creator>Isabella Velásquez</dc:creator><description><![CDATA[<p>At Posit, our mission is to empower data scientists and researchers with the best possible tools to explore, visualize, and share their work. For years, our team has relied on the <a href="https://jupyter.org/" target="_blank" rel="noopener">Jupyter</a> ecosystem, with Notebooks, IPython, JupyterLab, and JupyterHub all integrated across our product offerings and Jupyter kernels powering <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>&rsquo;s R and Python support. Today, we are proud to announce that we are deepening our commitment to this community by becoming an official <a href="https://jupyterfoundation.org/" target="_blank" rel="noopener">Jupyter Foundation</a> Member!</p>
<p>Since its inception, <a href="https://jupyter.org/" target="_blank" rel="noopener">Project Jupyter</a> has grown into one of the world&rsquo;s most widely used open source ecosystems for interactive computing, powering breakthroughs in research, education, and industry. The Jupyter Foundation, hosted by the <a href="https://training.linuxfoundation.org/" target="_blank" rel="noopener">Linux Foundation</a>, was established to support the long-term sustainability of this work by bringing together organizations committed to investing in the project&rsquo;s future.</p>
<p>By joining as a Foundation Member, we are becoming active stewards of its future. Our membership helps fund the core infrastructure, the release engineering, and the community events that keep Jupyter at the cutting edge of scientific computing. This move reinforces our promise to the open source community: we will not only build on these tools, but we will also build up the people and processes that create them.</p>
<p>Open source is a shared resource, and it requires shared responsibility. We encourage our partners and peers in the industry to join us in supporting the foundations that make our work possible.</p>
<p>To learn more about our commitment to open research and technical innovation, visit our <a href="https://opensource.posit.co/about/posit/" target="_blank" rel="noopener">About Page</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/featured.png" length="392627" type="image/png" />
    </item>
    <item>
      <title>Tidy Dev Day 2026</title>
      <link>https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/</guid>
      <dc:creator>Mine Çetinkaya-Rundel</dc:creator><description><![CDATA[<p>Tidy Dev Day (TDD) is a community event where R developers and open source contributors gather to collaboratively tackle real issues across the tidyverse ecosystem.
We&rsquo;ll have a curated slate of issues ready to go &mdash; you can dive in solo or pair up with another attendee or with one of our experienced developers.
TDD 2026 will be held on September 17, 2026 9 am - 4 pm CDT in Houston, TX, immediately following <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">posit::conf(2026)</a>.</p>
<h2 id="who-should-come">Who should come?
</h2>
<p>You don&rsquo;t need an extensive development background.
You don&rsquo;t need to have solved issues before or sent a pull request.
What you do need: a GitHub account and some basic familiarity with git.</p>
<p>In the weeks before TDD, on September 3, 2026 at 12-1 pm EDT, we&rsquo;ll hold optional virtual office hours so you can get your R environment, git workflow, GitHub setup, and IDE all humming in harmony &mdash; walk in on the day feeling completely ready.
Participants who have signed up by that date will receive an email with the Zoom link for the office hours.</p>
<h2 id="how-to-sign-up">How to sign up
</h2>
<p>There is a token $10 cost for the day; we provide the venue, snacks, lunch, and a barista. If cost is an issue, please email <a href="mailto:hadley@posit.co">hadley@posit.co</a> and we&rsquo;ll work that out.
We only charge a small fee to encourage people to show up for the day.</p>
<p><a href="https://luma.com/pnjuw08h" target="_blank" rel="noopener">Get your ticket now!</a></p>
<p>Note: By using this link, you agree to Posit&rsquo;s Privacy Policy, available at <a href="https://posit.co/about/privacy-policy" target="_blank" rel="noopener">https://posit.co/about/privacy-policy</a>.
You also consent to using <a href="https://lu.ma" target="_blank" rel="noopener">https://lu.ma</a> for the purposes of processing your event attendance fees.</p>
<p><a href="https://opensource.posit.co/tags/tidyverse-dev-day">Previous Tidy Dev Days</a> have sold out quickly, so don&rsquo;t wait!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/tdd-stickies.png" length="2725945" type="image/png" />
    </item>
    <item>
      <title>brulee 1.0.0</title>
      <link>https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/</link>
      <pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/</guid>
      <dc:creator>Max Kuhn</dc:creator><description><![CDATA[<p>We are thrilled to release version 1.0.0 of the brulee package. brulee contains regression and classification models built using the torch framework. To install the package, use:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;brulee&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;brulee&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="new-models">New Models
</h2>
<p>In the field of deep learning (DL), the term &ldquo;tabular&rdquo; refers to models that use common rectangular data sets, i.e., data in data frames or matrices. It specifically excludes raw images or unstructured text.</p>
<p>This release adds five new models:</p>
<ul>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=Revisiting&#43;deep&#43;learning&#43;models&#43;for&#43;tabular&#43;data&amp;as_ylo=2021&amp;as_yhi=2021&amp;btnG=" target="_blank" rel="noopener">Residual networks</a> (ResNet) via <code>brulee_resnet()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=Regularization&#43;learning&#43;networks%3A&#43;Deep&#43;learning&#43;for&#43;tabular&#43;datasets.&amp;btnG=" target="_blank" rel="noopener">Regularization learning networks</a> (RLN) with <code>brulee_rln()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=&#43;AutoInt&#43;Automatic&#43;feature&#43;interaction&#43;learning&#43;via&#43;self&#43;attentive&#43;neural&#43;networks&amp;as_ylo=2019&amp;as_yhi=2019&amp;btnG=" target="_blank" rel="noopener">AutoInt</a> via <code>brulee_auto_int()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=&#43;Saint&#43;Improved&#43;neural&#43;networks&#43;for&#43;tabular&#43;data&#43;via&#43;row&#43;attention&#43;and&#43;contrastive&#43;pre&#43;training&amp;as_ylo=2021&amp;as_yhi=2021&amp;btnG=" target="_blank" rel="noopener">Self-Attention and Inter-sample Attention Transformer</a> (Saint) using <code>brulee_saint()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=Chronos-2%3A&#43;From&#43;univariate&#43;to&#43;universal&#43;forecasting&amp;btnG=" target="_blank" rel="noopener">Chronos2</a> foundational model for forecasting via <code>brulee_chronos()</code></li>
</ul>
<p>These models share common argument names and have the same conventions as existing brulee models, such as <code>brulee_mlp()</code>. Many of these models incorporate modern neural network techniques, such as batch normalization, residual connections, and/or attention mechanisms.</p>
<p>For applications with large amounts of data, these modern techniques can make an already difficult optimization problem easier. The attention mechanisms also allow the model to be more expressive, leading to better performance.</p>
<p>Here&rsquo;s an example using AutoInt. The data are simulated values with 10 predictors, a regression equation containing numerous interaction terms, and a true RMSE of 0.25.</p>
<p>The model was trained and evaluated on a random 10% of the data to determine early stopping. Training (using the ADAMw optimizer) stopped after 5 bad epochs and reverted to the estimates at epoch 7.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">brulee</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidymodels</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">set.seed</span><span class="p">(</span><span class="m">83</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">train_data</span> <span class="o">&lt;-</span> <span class="nf">sim_regression</span><span class="p">(</span><span class="m">2000</span><span class="p">,</span> <span class="n">method</span> <span class="o">=</span> <span class="s">&#34;hooker_2004&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">test_data</span> <span class="o">&lt;-</span> <span class="nf">sim_regression</span><span class="p">(</span><span class="m">2000</span><span class="p">,</span> <span class="n">method</span> <span class="o">=</span> <span class="s">&#34;hooker_2004&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Put the predictors on the same scale:</span>
</span></span><span class="line"><span class="cl"><span class="n">rec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">recipe</span><span class="p">(</span><span class="n">outcome</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">train_data</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">step_normalize</span><span class="p">(</span><span class="nf">all_numeric_predictors</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set the R seed</span>
</span></span><span class="line"><span class="cl"><span class="nf">set.seed</span><span class="p">(</span><span class="m">13</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Set the torch seed (they don&#39;t need to be the same)</span>
</span></span><span class="line"><span class="cl"><span class="n">torch</span><span class="o">::</span><span class="nf">torch_manual_seed</span><span class="p">(</span><span class="m">13</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">autoint_fit</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">brulee_auto_int</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">rec</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">train_data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">num_embedding</span> <span class="o">=</span> <span class="m">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">verbose</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span></span></span></code></pre></div></div>
<pre><code>epoch: 000, learn rate: 0.01, Loss (scaled): 1.06
epoch: 1, learn rate: 0.01, Loss (scaled): 0.179
epoch: 2, learn rate: 0.01, Loss (scaled): 0.144
epoch: 3, learn rate: 0.01, Loss (scaled): 0.12
epoch: 4, learn rate: 0.01, Loss (scaled): 0.103
epoch: 5, learn rate: 0.01, Loss (scaled): 0.115
epoch: 6, learn rate: 0.01, Loss (scaled): 0.102
epoch: 7, learn rate: 0.01, Loss (scaled): 0.101
epoch: 8, learn rate: 0.01, Loss (scaled): 0.109
epoch: 9, learn rate: 0.01, Loss (scaled): 0.101
epoch: 10, learn rate: 0.01, Loss (scaled): 0.108
epoch: 11, learn rate: 0.01, Loss (scaled): 0.112
epoch: 12, learn rate: 0.01, Loss (scaled): 0.116
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">autoplot</span><span class="p">(</span><span class="n">autoint_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/index.markdown_strict_files/figure-markdown_strict/training-1.png" width="768" />
<p>One new addition to brulee is a collection of <code>summary()</code> methods that print layer dimensions and the corresponding number of parameters. This model is complex but, by deep learning standards, is small:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">summary</span><span class="p">(</span><span class="n">autoint_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>AutoInt architecture
inputs: 10 (0 categorical, 10 numeric) | output dim: 1

Embedding layer
  ContWeights(10 x 20)                200 params

Self-attention backbone (3 blocks, 2 heads)
  Linear(20 -&gt; 32)                    672 params
  MultiheadAttention(32, heads=2)   4,224 params
  MultiheadAttention(32, heads=2)   4,224 params
  MultiheadAttention(32, heads=2)   4,224 params
  + skip: Linear(20 -&gt; 32)            672 params
  ReLU                                  0 params
  output: 10 embeddings of dim 32 (flattened: 320)

Output head
  Linear(320 -&gt; 1)                    321 params

Total parameters: 14,537
</code></pre>
<p>How well does the model work on unseen data? Pretty well! The RMSE is fairly close to the best possible value.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">test_pred</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">predict</span><span class="p">(</span><span class="n">autoint_fit</span><span class="p">,</span> <span class="n">test_data</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">bind_cols</span><span class="p">(</span><span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">test_pred</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">ggplot</span><span class="p">(</span><span class="nf">aes</span><span class="p">(</span><span class="n">outcome</span><span class="p">,</span> <span class="n">.pred</span><span class="p">))</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">geom_point</span><span class="p">()</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">geom_abline</span><span class="p">(</span><span class="n">col</span> <span class="o">=</span> <span class="s">&#34;#8FDA04FF&#34;</span><span class="p">,</span> <span class="n">lty</span> <span class="o">=</span> <span class="m">2</span><span class="p">,</span> <span class="n">linewidth</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">geom_smooth</span><span class="p">(</span><span class="n">col</span> <span class="o">=</span> <span class="s">&#34;#D84D16FF&#34;</span><span class="p">,</span> <span class="n">se</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">coord_fixed</span><span class="p">(</span><span class="n">ratio</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/index.markdown_strict_files/figure-markdown_strict/evaluation-1.png" width="768" />
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">test_pred</span> <span class="o">|&gt;</span> <span class="nf">metrics</span><span class="p">(</span><span class="n">outcome</span><span class="p">,</span> <span class="n">.pred</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 3 × 3
  .metric .estimator .estimate
  &lt;chr&gt;   &lt;chr&gt;          &lt;dbl&gt;
1 rmse    standard       0.316
2 rsq     standard       0.898
3 mae     standard       0.251
</code></pre>
<p>One important note: the Chronos model is a pretrained foundational model for forecasting. On its first use, it will download the model weights. These are about 200 MB in size and are cached locally, so this download should occur only once. This is similar to how our package for the <a href="https://opensource.posit.co/blog/2026-03-31_tabpfn-0-1-0">TabPFN foundational model</a> operates.</p>
<h2 id="numerical-changes">Numerical Changes
</h2>
<p>This version breaks backward compatibility with previous versions, but for good reasons.</p>
<p>The first is to switch from 64-bit precision to 32-bit. This is a common strategy (and many DL models go down to 16- or 8-bit precision). This should make models run faster and have a smaller memory footprint, but the main reason was to enable GPU computations on Apple machines. Note, however, that for the data set and model sizes shown in the example above, there is limited efficiency gain on the GPU. Since the computational load is relatively light (by DL standards), the GPU spends most of its time idle, so it isn&rsquo;t much faster than the CPU. With larger data sets and models like Saint or Chronos, there should be some time savings. Overall, though, there should be drastic memory savings when using the GPU compared to pure CPU computations.</p>
<p>We&rsquo;ve also made an effort to increase the numerical stability of these methods. This involves a more pervasive gradient clipping strategy and, for classification models, a more numerically stable implementation of the cross-entropy objective function.</p>
<p>One small improvement concerns reproducibility. torch has some inconsistencies in how random numbers are handled for GPU computations. When initializing parameters for these models, the random numbers are generated on the CPU and then moved to the GPU. This allows the model to start from the same place independently of where the subsequent computations occur.</p>
<h2 id="whats-next">What&rsquo;s Next
</h2>
<p>For brulee, the next big step is an R torch implementation of the <a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=&#43;TabICLv2&#43;A&#43;better&#43;faster&#43;scalable&#43;and&#43;open&#43;tabular&#43;foundation&#43;model&amp;as_ylo=2026&amp;as_yhi=2026&amp;btnG=" target="_blank" rel="noopener">TabICL foundational model</a>. This shows great promise but has a large computational footprint (in terms of code and system resources). Like Chronos, it will also require downloading pretrained model weights. We have a working version but are conducting additional testing before we release it.</p>
<p>Outside of brulee, we&rsquo;re working on a parsnip interface to these models. Deep neural networks tend to have many tuning parameters, and many of them substantially affect model performance. The extension will include a function to make efficient space-filling designs for models with multiple hidden layers.</p>
<h2 id="thanks">Thanks
</h2>
<p>Banner image by <a href="https://unsplash.com/@lauraperuchi" target="_blank" rel="noopener">Laura Peruchi</a>.</p>
<p>Thanks to contributors <a href="https://github.com/edgararuiz" target="_blank" rel="noopener">@edgararuiz</a>, <a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>, <a href="https://github.com/lizelgreyling" target="_blank" rel="noopener">@lizelgreyling</a>, and <a href="https://github.com/Wander03" target="_blank" rel="noopener">@Wander03</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/brulee.jpeg" length="953553" type="image/jpeg" />
    </item>
    <item>
      <title>Introducing debrief: profiling summaries for AI agents</title>
      <link>https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/</link>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/</guid>
      <dc:creator>Emil Hvitfeldt</dc:creator><description><![CDATA[<p>While AI agents are getting better at writing code,
they sometimes struggle with making the code faster.
We have a number of tools at our disposal to do this,
but some of them are not made with AI agents in mind.
The <a href="https://profvis.r-lib.org/" target="_blank" rel="noopener">profvis</a> package is one such example.
The interactive flame graph it produces is fantastic for human eyes,
it allows us to easily see the broad picture while also drilling down into the details.
This is what started the idea for this new package; debrief.
Allowing the AI agent to read the profvis results as text,
thus letting it make more informed decisions about how to optimize the code.</p>
<p>debrief is on CRAN:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;debrief&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># or the development version:</span>
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;r-lib/debrief&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="from-guessing-to-measuring">From guessing to measuring
</h2>
<p>Prompt an AI agent to make your R code faster,
and it will either declare that a bottleneck exists without evidence,
or try to use <code>Rprof()</code> directly.
We find that <code>profvis()</code> is better and easier to use than <code>Rprof()</code> directly,
as it provides a more user-friendly interface and visualizations.
But that interface is designed for human eyes,
not for an LLM reading a transcript.</p>
<p>This is where debrief comes in.
It turns the output of <code>profvis()</code> into a text-based summary that can be easily read and understood by AI agents.
Every function is prefixed <code>pv_</code> (for profvis) and returns text designed to be read top to bottom:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">debrief</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">p</span> <span class="o">&lt;-</span> <span class="n">profvis</span><span class="o">::</span><span class="nf">profvis</span><span class="p">(</span><span class="nf">slow_function</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nf">pv_print_debrief</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ## PROFILING SUMMARY</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Total time: 190 ms (19 samples @ 10 ms interval)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Source references: available</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### TOP FUNCTIONS BY SELF-TIME</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    110 ms ( 57.9%)  paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  rnorm</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  unlist</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### TOP FUNCTIONS BY TOTAL TIME</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    140 ms ( 73.7%)  summarize_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    110 ms ( 57.9%)  paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     30 ms ( 15.8%)  clean_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  [.data.frame</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### HOT LINES (by self-time)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    120 ms ( 63.2%)  analysis.R:22</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                    list(</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  analysis.R:9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                    x &lt;- rnorm(n)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### HOT CALL PATHS</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 110 ms (57.9%) - 11 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; summarize_data (analysis.R:5)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; paste (analysis.R:22)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     base::tryCatch</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; [.data.frame (analysis.R:15)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; cut.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; scale.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### MEMORY ALLOCATION (by function)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    51.28 MB paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    10.01 MB anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     2.90 MB any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     1.62 MB rnorm</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### MEMORY ALLOCATION (by line)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    51.28 MB analysis.R:22</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;             list(</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     1.62 MB analysis.R:9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;             x &lt;- rnorm(n)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### Next steps</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_focus(p, &#34;paste&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_source_context(p, &#34;analysis.R&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_suggestions(p)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_help()</span></span></span></code></pre></div></div>
<p><code>pv_print_debrief()</code> is meant as the first entry point after profiling.
It gives a comprehensive overview of the profile with a number of sections.</p>
<ul>
<li>Top functions by self-time and total time</li>
<li>Hot lines and hot call paths</li>
<li>Memory allocation by function and by line</li>
<li>Next steps</li>
</ul>
<p>Both the hot lines and hot paths sections link back to source references when available,
Especially relevant if the bottleneck is happening in a common function name like <code>paste()</code> that is scattered across your package.
With this, we know exactly which <code>paste()</code> call is the culprit.</p>
<p>Each function in {debrief} also prints the name of the possible next functions to run,
so the assistant can drill down into the details.</p>
<p>debrief is a new package.
Its output almost certainly isn&rsquo;t part of any model&rsquo;s training data.
And output is also very unlikely to be added,
as profiling code is rarely committed.
This means that the package tries to be self-documenting by design,
With the hope that an AI agent doesn&rsquo;t need to read the documentation to understand how to use it.</p>
<h2 id="getting-an-agent-into-the-session">Getting an agent into the session
</h2>
<p>While you can use debrief with any AI agent,
it works better with agents that keep a persistent R session.
Otherwise, it will need to rerun the profile every time it wants to check the results of an edit.
You also won&rsquo;t be able to take full advantage of the &ldquo;Next steps&rdquo; hints,
which are designed to guide the agent through drilling down into the profile.</p>
<p><a href="https://posit.com/assistant" target="_blank" rel="noopener">Posit Assistant</a> is the easiest way to do this.
It runs in the same session as you, so you can be a bigger part of the process.
The same assistant runs in Positron, RStudio, or the terminal,
so you can profile interactively or drive the whole loop headlessly from the <code>pa</code> CLI.</p>
<p>If you aren&rsquo;t using RStudio or Positron, or prefer an agent like Claude Code or Codex,
<a href="https://github.com/posit-dev/mcp-repl" target="_blank" rel="noopener">mcp-repl</a> gives you the same thing.
It&rsquo;s an MCP server that hands any MCP-capable agent a persistent R (or Python) session,
so you can load debrief and run the profiler once, then drill down as needed.</p>
<h2 id="trying-it-out">Trying it out
</h2>
<p>The point of debrief is to put profiling results in a form an agent can easily interact with,
so it can measure instead of guess and iterate toward faster code.
We have already used it this way:
<a href="https://github.com/tidymodels/textrecipes/pull/309" target="_blank" rel="noopener">tidymodels/textrecipes#309</a>
is a profiling-driven optimization of <code>step_word_embeddings()</code>,
where each round of profile, read, change, and re-measure was guided by debrief&rsquo;s summaries.</p>
<p>Install debrief, point an agent at a slow function, and let it profile.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/featured.png" length="332953" type="image/png" />
    </item>
    <item>
      <title>pkgsite 0.1.0: Convert your `.Rd` files to Quarto</title>
      <link>https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/</link>
      <pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/</guid>
      <dc:creator>Edgar Ruiz</dc:creator><description><![CDATA[<p>We are happy to introduce <code>pkgsite</code>. It reads the compiled <code>.Rd</code> files in your
R package and converts them into <code>.qmd</code> files. It can also automatically create
a reference index page that lists all your exported functions. You can customize
the format of both using templates, then let Quarto render the HTML. <code>pkgsite</code>
is now available on CRAN, to install use:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;pkgsite&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p><code>pkgsite</code> is inspired by Python&rsquo;s <a href="https://machow.github.io/quartodoc/get-started/overview.html" target="_blank" rel="noopener"><code>Quartodoc</code></a>,
which does the same for Python packages.</p>
<h2 id="why-pkgsite">Why <code>pkgsite</code>?
</h2>
<p>The main website builder for R packages is <a href="https://pkgdown.r-lib.org/" target="_blank" rel="noopener"><code>pkgdown</code></a>.
It goes all the way to a finished, publication-ready HTML website in one step,
and for most packages that is exactly what you want. It is mature, has a large
ecosystem of themes and extensions, and just works.</p>
<p><code>pkgsite</code>, on the other hand, only creates <code>.qmd</code> files with the help content.
You decide how to structure the site around them, and Quarto handles the final
HTML output.</p>
<p>To see what that looks like, compare the
<a href="https://github.com/mlverse/mall/blob/main/r/man/llm_sentiment.Rd" target="_blank" rel="noopener">source <code>.Rd</code> file</a>
for <code>llm_sentiment()</code> in <code>mall</code> with the
<a href="https://github.com/mlverse/mall/blob/main/reference/llm_sentiment.qmd" target="_blank" rel="noopener"><code>.qmd</code> file <code>pkgsite</code> generated from it</a>.</p>
<p>Two cases where we have seen it make a difference are:</p>
<ul>
<li>Examples that require local resources</li>
<li>Unified R and Python Quarto sites</li>
</ul>
<h3 id="examples-that-require-local-resources">Examples that require local resources
</h3>
<p>Some packages depend on things that are not available on automated build and
publishing platforms such as GitHub Actions or Netlify: databases, large
language models, or Spark clusters. Running their examples there is not
feasible, yet you still want working, rendered documentation.</p>
<p>Quarto&rsquo;s <a href="https://quarto.org/docs/projects/code-execution.html#freeze" target="_blank" rel="noopener">freeze</a>
solves this cleanly. You render the site once locally where those resources are
available, commit the <code>_freeze/</code> folder alongside your source, and GitHub
rebuilds the site on every push without re-executing a single line of code.</p>
<p>The <a href="https://mlverse.github.io/mall/" target="_blank" rel="noopener"><code>mall</code></a> and
<a href="https://mlverse.github.io/lang/" target="_blank" rel="noopener"><code>lang</code></a> packages are concrete examples.
Their function examples call an LLM, so they cannot run on those platforms.
With <code>pkgsite</code> and freeze, rendering happens on a developer machine where the
model is accessible, and the frozen output travels with the repository.</p>
<p>In the <code>llm_sentiment()</code> function, the example section looks like this in the
resulting <code>.qmd</code> file:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Examples
</span></span></span><span class="line"><span class="cl">``<span class="sb">`{r}
</span></span></span><span class="line"><span class="cl"><span class="sb">library(mall)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">data(&#34;reviews&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">llm_use(&#34;ollama&#34;, &#34;llama3.2&#34;, seed = 100, .silent = TRUE)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">llm_sentiment(reviews, review)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb"># Use &#39;pred_name&#39; to customize the new column&#39;s name
</span></span></span><span class="line"><span class="cl"><span class="sb">llm_sentiment(reviews, review, pred_name = &#34;review_sentiment&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<p>And this is what the <a href="https://mlverse.github.io/mall/reference/llm_sentiment.html#examples" target="_blank" rel="noopener">rendered page</a> looks like on the <code>mall</code> website:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/llm-sentiment-page.png"
      alt="The rendered llm_sentiment() reference page on the mall website, showing the function description and executed example output."  title="Examples rendered locally using Quarto freeze" 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Examples rendered locally using Quarto freeze</figcaption>
  </figure></div>
</p>
<h3 id="unified-r-and-python-sites">Unified R and Python sites
</h3>
<p>If your project ships both an R package and a Python package, you can combine
<code>pkgsite</code>&rsquo;s output with
<a href="https://machow.github.io/quartodoc/get-started/overview.html" target="_blank" rel="noopener"><code>Quartodoc</code></a>&rsquo;s
output into a single Quarto website. Both tools write <code>.qmd</code> reference pages
that Quarto assembles together, giving R and Python users a consistent
experience on one site. The <code>mall</code> package&rsquo;s
<a href="https://mlverse.github.io/mall/reference/" target="_blank" rel="noopener">reference section</a> is a live
example: R and Python pages side by side, built from two different tools,
served as one site.</p>
<h2 id="getting-started">Getting started
</h2>
<p>From within your package directory, one function call does the work:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">pkgsite</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">write_reference</span><span class="p">()</span></span></span></code></pre></div></div>
<p><code>write_reference()</code> creates a <code>reference/index.qmd</code> that links to all your
exported functions, and converts each <code>.Rd</code> file in <code>man/</code> into its own <code>.qmd</code>
reference page.</p>
<p>You can customize its behavior through arguments. For example, if you want to
skip running the examples when Quarto renders the reference pages:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">write_reference</span><span class="p">(</span><span class="n">examples</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span> <span class="n">not_run_examples</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Calling it without arguments reads any configuration from a <code>pkgsite:</code> section
at the top level of <code>_quarto.yml</code>. Following the same convention as
<a href="https://machow.github.io/quartodoc/get-started/overview.html" target="_blank" rel="noopener"><code>Quartodoc</code></a>,
the Python equivalent, this is where you set the package root, the output
folder, templates, and optionally how functions are grouped and ordered in the
index:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">pkgsite</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dir</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;.&#34;</span><span class="w">                    </span><span class="c"># path to the package root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">reference</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">dir</span><span class="p">:</span><span class="w"> </span><span class="l">reference           </span><span class="w"> </span><span class="c"># where to write the .qmd files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">not_run_examples</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">   </span><span class="c"># whether to execute \dontrun{} examples</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">template</span><span class="p">:</span><span class="w"> </span><span class="l">inst/templates/_reference.qmd  </span><span class="w"> </span><span class="c"># custom page template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">index</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">file</span><span class="p">:</span><span class="w"> </span><span class="l">index.qmd        </span><span class="w"> </span><span class="c"># name of the index file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">template</span><span class="p">:</span><span class="w"> </span><span class="l">inst/templates/_index.qmd    </span><span class="w"> </span><span class="c"># custom index template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">contents:               # optional</span><span class="p">:</span><span class="w"> </span><span class="l">custom function grouping</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">section</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Write files&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">contents</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">write_reference.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">write_reference_index.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">write_reference_pages.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">section</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Conversion&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">contents</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">rd_to_qmd.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">rd_to_list.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">index_to_qmd.qmd</span></span></span></code></pre></div></div>
<p>If you omit <code>contents</code>, <code>pkgsite</code> falls back to grouping by <code>roxygen2</code>
<code>@family</code> tags, then alphabetical order. You only need to re-run
<code>write_reference()</code> when you add, rename, or remove exported functions.</p>
<p>Use arguments for one-off adjustments; the <code>_quarto.yml</code> configuration is the
better choice when you want those settings to apply consistently every time
<code>write_reference()</code> is called.</p>
<p>This is what the rendered reference index page looks like on the <code>pkgsite</code>
website, using the grouping specified in the example YAML above:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/pkgsite-reference-index.png"
      alt="The pkgsite reference index page, with exported functions organized into named sections like &ldquo;Write files&rdquo; and &ldquo;Conversion&rdquo;."  title="Group functions into sections in the YAML file" 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Group functions into sections in the YAML file</figcaption>
  </figure></div>
</p>
<h2 id="customizing-the-page-layout">Customizing the page layout
</h2>
<p>The layout of every reference page and the index is driven by a Quarto template
file that uses four-curly-brace placeholders like <code>{{{{title.description}}}}</code>.
The prefix — <code>title.</code> or <code>notitle.</code> — controls whether the section heading is
included. The defaults work well for most packages, but if you want to
re-order sections, add a logo, link to source code, or adjust per-page
frontmatter, you can supply your own template. For example, a minimal template
that shows only the title, description, and examples looks like this:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;{{{{notitle.title}}}}&#34;
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## {{{{notitle.title}}}}
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Description
</span></span></span><span class="line"><span class="cl">{{{{title.description}}}}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Examples
</span></span></span><span class="line"><span class="cl">{{{{title.examples}}}}</span></span></code></pre></div></div>
<p>The
<a href="https://edgararuiz.github.io/pkgsite/articles/customize.html" target="_blank" rel="noopener">Customize the pages</a>
article covers the full set of available placeholders.</p>
<p>The <code>mall</code> package is a good example of this. Its custom template makes two
additions to the default: it adjusts how <code>knitr</code> renders table column widths,
and it adds a link to the R source code of each function on GitHub:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/mall-custom-template.png"
      alt="A mall reference page built from a custom template, with a &ldquo;Source code&rdquo; link to the function&rsquo;s R source file on GitHub."  title="Add custom elements to reference pages with a template" 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Add custom elements to reference pages with a template</figcaption>
  </figure></div>
</p>
<h2 id="publishing-to-github-pages">Publishing to GitHub Pages
</h2>
<p>To publish your site on every push to <code>main</code>, you need a GitHub Actions
workflow that runs <code>quarto render</code> and deploys the output to the <code>gh-pages</code>
branch. The
<a href="https://edgararuiz.github.io/pkgsite/articles/github-actions.html" target="_blank" rel="noopener">GitHub Pages</a>
article on the <code>pkgsite</code> website walks through the full setup with a working
example.</p>
<h3 id="auto-linking-function-names">Auto-linking function names
</h3>
<p><code>pkgsite</code> can turn every function name in your prose into a link pointing to
its own reference page automatically. Mention <code>llm_sentiment()</code> anywhere in
your documentation and it becomes a clickable link to the <code>llm_sentiment</code>
reference page, no extra markup needed. The same
<a href="https://edgararuiz.github.io/pkgsite/articles/github-actions.html" target="_blank" rel="noopener">GitHub Pages</a>
article covers how to enable it.</p>
<h2 id="learn-more">Learn more
</h2>
<p>The full documentation lives at
<a href="https://edgararuiz.github.io/pkgsite/" target="_blank" rel="noopener">edgararuiz.github.io/pkgsite</a>, and the
source is on <a href="https://github.com/edgararuiz/pkgsite" target="_blank" rel="noopener">GitHub</a>. Issues and
feature requests go to the
<a href="https://github.com/edgararuiz/pkgsite/issues" target="_blank" rel="noopener">issue tracker</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/logo.png" length="1076855" type="image/png" />
    </item>
    <item>
      <title>webR 0.6.0</title>
      <link>https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/</link>
      <pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/</guid>
      <dc:creator>George Stagg</dc:creator><description><![CDATA[<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
<style>
  .webr-run-button {
    background-color: #F7F7F7;
    border: 1px solid #EEE;
    padding: 0.1em 0.5em;
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    cursor: pointer;
    font-size: 0.9em;
  }
  .webr-run-button:hover {
    background-color: #EEE;
  }
  .webr-run-button:active {
    background-color: #DDD;
  }
  .webr-run-button:disabled {
    background-color: #DDD;
    cursor: not-allowed;
    transform: none;
  }
  .webr-code-output pre {
    margin-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/r/r.js"></script>
<script type="module">
  import { WebR } from 'https://webr.r-wasm.org/latest/webr.mjs';
  globalThis.webRIndex = 0;
  globalThis.webREditorCode = [];
  globalThis.webR = new WebR();
  globalThis.webRPlotWidth = 640;
  globalThis.webRPlotHeight = 400;
  await globalThis.webR.init();
  await webR.evalRVoid("webr::shim_install()");
  await webR.evalRVoid("webr::global_prompt_install()", { withHandlers: false });
  globalThis.webRCodeShelter = await new globalThis.webR.Shelter();
  document.querySelectorAll(".webr-run-button").forEach((btn) => {
    btn.innerText = 'Run code';
    btn.disabled = false;
  });
</script>
<p>We&rsquo;re delighted to announce the release of webR 0.6.0. WebR brings R to the web browser using WebAssembly, powering <a href="https://r-wasm.github.io/quarto-live/" target="_blank" rel="noopener">Quarto Live</a>, <a href="https://shinylive.io/r/examples/" target="_blank" rel="noopener">Shinylive</a>, and other interactive R experiences.</p>
<p>As a reminder of what webR can do, here is an interactive code editor running R in the browser. Try it out!</p>
<script type="module">
 globalThis.webREditorCode.push(`model <- lm(body_mass ~ bill_len, penguins)
summary(model)

boxplot(
  penguins$body_mass ~ penguins$species,
  xlab = 'Species',
  ylab = 'Body Mass (g)',
  col = c('#06a', '#f80', '#085')
)`)
</script>
<div><button class="webr-run-button" disabled type="button">Loading webR...</button></div>
<div class="webr-editor"></div>
<div class="webr-code-output"><pre style="visibility: hidden"></pre></div>
<script type="module">
  const runButton = document.getElementsByClassName('webr-run-button')[globalThis.webRIndex];
  const editorDiv = document.getElementsByClassName('webr-editor')[globalThis.webRIndex];
  const outputDiv = document.getElementsByClassName('webr-code-output')[globalThis.webRIndex];
  const codeValue = globalThis.webREditorCode[globalThis.webRIndex];
  globalThis.webRIndex++;
  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    editorDiv.append(elt);
  },{
    value: codeValue,
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });
  runButton.onclick = async () => {
    runButton.disabled = true;
    let canvas = undefined;
    await webR.init();
    await webR.evalRVoid(`webr::canvas(width = ${globalThis.webRPlotWidth}, height = ${globalThis.webRPlotHeight})`);
    const result = await webRCodeShelter.captureR(editor.getValue(), {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false,
      captureGraphics: false,
      env: {},
    });
    try {
      await webR.evalRVoid("dev.off()");
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data).join('\n');
      outputDiv.innerHTML = '';
      const pre = document.createElement("pre");
      if (/\S/.test(out)) {
        const code = document.createElement("code");
        code.innerText = out;
        pre.appendChild(code);
      } else {
        pre.style.visibility = 'hidden';
      }
      outputDiv.appendChild(pre);
      const msgs = await webR.flush();
      msgs.forEach(msg => {
        if (msg.type === 'canvas'){
          if (msg.data.event === 'canvasImage') {
            canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
          } else if (msg.data.event === 'canvasNewPage') {
            canvas = document.createElement('canvas');
            canvas.setAttribute('width', 2 * globalThis.webRPlotWidth);
            canvas.setAttribute('height', 2 * globalThis.webRPlotHeight);
            canvas.style.width="640px";
            canvas.style.display="block";
            canvas.style.margin="auto";
            const p = document.createElement("p");
            p.appendChild(canvas);
            outputDiv.appendChild(p);
          }
        }
      });
    } finally {
      webRCodeShelter.purge();
      runButton.disabled = false;
    }
  }
</script>
<p>It&rsquo;s been almost a year since I&rsquo;ve written a blog post about webR, and so this post highlights some key features and improvements over the last few releases. For a full list of changes, see the recent <a href="https://github.com/r-wasm/webr/releases" target="_blank" rel="noopener">release notes on GitHub</a>.</p>
<h2 id="updates-to-r-emscripten-and-llvm">Updates to R, Emscripten, and LLVM
</h2>
<p>We&rsquo;ve updated the version of R on which webR is based to version 4.6.0, ensuring we have all the latest improvements and bug fixes from the R core team. For example, the new <code>%notin%</code> operator can now be used:</p>
<script type="module">
 globalThis.webREditorCode.push("sessionInfo()[[1]]$version.string\n4 %notin% 1:10\n4 %notin% c(1, 2, 3)")
</script>
<div><button class="webr-run-button" disabled type="button">Loading webR...</button></div>
<div class="webr-editor"></div>
<div class="webr-code-output"><pre style="visibility: hidden"></pre></div>
<script type="module">
  const runButton = document.getElementsByClassName('webr-run-button')[globalThis.webRIndex];
  const editorDiv = document.getElementsByClassName('webr-editor')[globalThis.webRIndex];
  const outputDiv = document.getElementsByClassName('webr-code-output')[globalThis.webRIndex];
  const codeValue = globalThis.webREditorCode[globalThis.webRIndex];
  globalThis.webRIndex++;
  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    editorDiv.append(elt);
  },{
    value: codeValue,
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });
  runButton.onclick = async () => {
    runButton.disabled = true;
    let canvas = undefined;
    await webR.init();
    await webR.evalRVoid(`webr::canvas(width = ${globalThis.webRPlotWidth}, height = ${globalThis.webRPlotHeight})`);
    const result = await webRCodeShelter.captureR(editor.getValue(), {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false,
      captureGraphics: false,
      env: {},
    });
    try {
      await webR.evalRVoid("dev.off()");
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data).join('\n');
      outputDiv.innerHTML = '';
      const pre = document.createElement("pre");
      if (/\S/.test(out)) {
        const code = document.createElement("code");
        code.innerText = out;
        pre.appendChild(code);
      } else {
        pre.style.visibility = 'hidden';
      }
      outputDiv.appendChild(pre);
      const msgs = await webR.flush();
      msgs.forEach(msg => {
        if (msg.type === 'canvas'){
          if (msg.data.event === 'canvasImage') {
            canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
          } else if (msg.data.event === 'canvasNewPage') {
            canvas = document.createElement('canvas');
            canvas.setAttribute('width', 2 * globalThis.webRPlotWidth);
            canvas.setAttribute('height', 2 * globalThis.webRPlotHeight);
            canvas.style.width="640px";
            canvas.style.display="block";
            canvas.style.margin="auto";
            const p = document.createElement("p");
            p.appendChild(canvas);
            outputDiv.appendChild(p);
          }
        }
      });
    } finally {
      webRCodeShelter.purge();
      runButton.disabled = false;
    }
  }
</script>
<p>Under the hood, we&rsquo;ve also upgraded <a href="https://emscripten.org" target="_blank" rel="noopener">Emscripten</a> to version 5.0.7. Emscripten serves as the crucial layer between the web browser and R&rsquo;s source code, playing a role similar to an operating system. We&rsquo;ve also updated the version of LLVM we use to compile Fortran sources to 21.1.8.</p>
<h3 id="improvements-to-fortran-on-webassembly">Improvements to Fortran on WebAssembly
</h3>
<p>The R source code contains a surprising amount of Fortran code (the bulk of which written in F77-style), even today. For webR, we rely on a forked version of LLVM Flang with custom patches to add support for emitting WebAssembly. I won&rsquo;t go into the deep technical details here, but I have <a href="https://gws.phd/posts/fortran_wasm/" target="_blank" rel="noopener">written about it before</a> if you are interested.</p>
<p>For a long time, dynamic array operations introduced in more modern Fortran standards were not supported by our fork. This meant that some packages which use these Fortran features would not work in webR, crashing with a dense and largely unhelpful runtime error message:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fortran" data-lang="fortran"><span class="line"><span class="cl"><span class="k">program</span><span class="w"> </span><span class="n">p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">integer</span><span class="w"> </span><span class="kd">::</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">print</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"> </span><span class="nb">sum</span><span class="p">(</span><span class="n">a</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">end</span><span class="w"> </span><span class="k">program</span></span></span></code></pre></div></div>
<pre><code>$ ./previous/flang -c arr.f90 -o arr.o
$ emcc arr.o libFortranRuntime.a -o arr.js
$ node arr.js
  fatal Fortran runtime error(...reduction-templates.h(90)): Internal error:
  RUNTIME_CHECK(TypeCode(CAT, KIND) == x.type() || ...) failed
</code></pre>
<p>However, thanks to a <a href="https://github.com/r-wasm/flang-wasm/issues/9" target="_blank" rel="noopener">hint originating from a community member</a>, we&rsquo;ve now added a workaround for the underlying compiler problem, and R packages which rely on these Fortran features should now work in webR without crashing:</p>
<pre><code>$ ./latest/flang -c arr.f90 -o arr.o
$ emcc arr.o libFortranRuntime.a -o arr.js
$ node arr.js
  15
</code></pre>
<h2 id="additional-system-libraries">Additional system libraries
</h2>
<p>We&rsquo;ve also added or updated the following system libraries compiled to WebAssembly, which are now available for use by R packages in webR:</p>
<ul>
<li>cacert.pem 2026-05-14</li>
<li>fontconfig v2.15.0</li>
<li>libpoppler v24.12.0</li>
<li>libsodium v1.0.22</li>
<li>libtiff v4.7.1</li>
<li>libuv v1.44.2</li>
<li>openssl v3.5.1</li>
</ul>
<h2 id="asyncawait-in-webreval_js">Async/await in <code>webr::eval_js()</code>
</h2>
<p>One of the biggest issues when working with webR&rsquo;s JavaScript API is that we must block the worker thread running the WebAssembly R process. This is required to make sure we can wait and take input from the user, both at the top-level and in functions like <code>readline()</code> or <code>browser()</code>. Previously, this meant that only synchronous JavaScript code could be run from R via <code>webr::eval_js()</code>, and JavaScript features like <code>Promise</code>, <code>async</code> or <code>await</code> were not available.</p>
<p>By proxying the JavaScript code to the main thread, we&rsquo;ve added support for running asynchronous JavaScript code in <code>webr::eval_js()</code>. You can now use <code>await = TRUE</code> to wait for a <code>Promise</code> to resolve before returning a value to R:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">webr</span><span class="o">::</span><span class="nf">eval_js</span><span class="p">(</span><span class="s">&#34;new Promise((res) =&gt; res(1729))&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>[1] 1729
</code></pre>
<p>Or equivalently, you can use <code>await</code> inside an <code>async</code> JavaScript function:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">webr</span><span class="o">::</span><span class="nf">eval_js</span><span class="p">(</span><span class="n">r</span><span class="s">&#34;{
</span></span></span><span class="line"><span class="cl"><span class="s">  (async () =&gt; {
</span></span></span><span class="line"><span class="cl"><span class="s">    await new Promise(r =&gt; setTimeout(r, 500));
</span></span></span><span class="line"><span class="cl"><span class="s">    return 87539319;
</span></span></span><span class="line"><span class="cl"><span class="s">  })();
</span></span></span><span class="line"><span class="cl"><span class="s">}&#34;</span><span class="p">,</span> <span class="n">await</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>[1] 87539319
</code></pre>
<h2 id="using-curl-under-webassembly">Using curl under WebAssembly
</h2>
<p>Using similar techniques, we have also added support for proxying WebSocket traffic via the main thread. This, combined with some <a href="https://jeroen.github.io/notes/webassembly-curl/" target="_blank" rel="noopener">other work for libcurl in WebAssembly</a>, means we can now use the curl and httr2 R packages in webR.</p>
<script type="module">
 globalThis.webREditorCode.push(`# Load the R package
library(httr2)

# Create a request
req <- request("https://r-project.org")
req`)
</script>
<div><button class="webr-run-button" disabled type="button">Loading webR...</button></div>
<div class="webr-editor"></div>
<div class="webr-code-output"><pre style="visibility: hidden"></pre></div>
<script type="module">
  const runButton = document.getElementsByClassName('webr-run-button')[globalThis.webRIndex];
  const editorDiv = document.getElementsByClassName('webr-editor')[globalThis.webRIndex];
  const outputDiv = document.getElementsByClassName('webr-code-output')[globalThis.webRIndex];
  const codeValue = globalThis.webREditorCode[globalThis.webRIndex];
  globalThis.webRIndex++;
  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    editorDiv.append(elt);
  },{
    value: codeValue,
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });
  runButton.onclick = async () => {
    runButton.disabled = true;
    let canvas = undefined;
    await webR.init();
    await webR.evalRVoid(`webr::canvas(width = ${globalThis.webRPlotWidth}, height = ${globalThis.webRPlotHeight})`);
    const result = await webRCodeShelter.captureR(editor.getValue(), {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false,
      captureGraphics: false,
      env: {},
    });
    try {
      await webR.evalRVoid("dev.off()");
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data).join('\n');
      outputDiv.innerHTML = '';
      const pre = document.createElement("pre");
      if (/\S/.test(out)) {
        const code = document.createElement("code");
        code.innerText = out;
        pre.appendChild(code);
      } else {
        pre.style.visibility = 'hidden';
      }
      outputDiv.appendChild(pre);
      const msgs = await webR.flush();
      msgs.forEach(msg => {
        if (msg.type === 'canvas'){
          if (msg.data.event === 'canvasImage') {
            canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
          } else if (msg.data.event === 'canvasNewPage') {
            canvas = document.createElement('canvas');
            canvas.setAttribute('width', 2 * globalThis.webRPlotWidth);
            canvas.setAttribute('height', 2 * globalThis.webRPlotHeight);
            canvas.style.width="640px";
            canvas.style.display="block";
            canvas.style.margin="auto";
            const p = document.createElement("p");
            p.appendChild(canvas);
            outputDiv.appendChild(p);
          }
        }
      });
    } finally {
      webRCodeShelter.purge();
      runButton.disabled = false;
    }
  }
</script>
<p>Try out a <a href="https://webr.r-wasm.org/latest/#code=eJxdjzFuwkAQRfs9xQoaWwpeQYmiNGlTREipV4M9sU3s3c3MWAiJjhNwBzpukDr3IstipIhq%2Fteb%2FzVzPJwc9Hj%2BcNJKh9W8WJ0CSPNjGt%2Bj2eLaDoxk%2FvMKBH4vU%2F3modLSoF7pAOUX1KhaxwJdV4yes0kjQotJrrp2TUC7LPlcqal%2BJQRBDZrwe0AWFad%2Bnt1tigZeGkOzQH6DpRSe6lgVF675d6RPT326YMw8RSEDudbVqZeDd4zqKsZmG26pLOr8BvYvadOW3gk6sbILmD2w%2BJUMbCvkckR%2Fegdw2w%3D%3D" target="_blank" rel="noopener">full example R script using httr2 in the webR app</a>.</p>
<h2 id="dark-mode-for-the-webr-app">Dark mode for the webR app
</h2>
<p>Speaking of the webR app, if your system is set to dark mode the webR app will now automatically switch to a dark theme. This feature was contributed by an attendee of <a href="https://tidyverse.org/tags/tidyverse-dev-day/" target="_blank" rel="noopener">Tidyverse Developer Day 2025</a>. Thanks, Kyle!</p>
<figure>
<img src="https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/dark.png" data-fig-alt="A screenshot of the webR app. On the left shows the app with light mode theme. On the right shows the app in dark mode theme." alt="The webR app in light and dark." />
<figcaption aria-hidden="true">The webR app in light and dark.</figcaption>
</figure>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>And, as always, a huge thank you to everyone else who has contributed to webR, either by submitting bug reports, contributing code, or just sharing feedback.</p>
<p><a href="https://github.com/AABoyles" target="_blank" rel="noopener">@AABoyles</a>, <a href="https://github.com/ai-petri" target="_blank" rel="noopener">@ai-petri</a>, <a href="https://github.com/brendanhcullen" target="_blank" rel="noopener">@brendanhcullen</a>, <a href="https://github.com/bryce-carson" target="_blank" rel="noopener">@bryce-carson</a>, <a href="https://github.com/chizapoth" target="_blank" rel="noopener">@chizapoth</a>, <a href="https://github.com/coatless" target="_blank" rel="noopener">@coatless</a>, <a href="https://github.com/daniel-woodie" target="_blank" rel="noopener">@daniel-woodie</a>, <a href="https://github.com/dipterix" target="_blank" rel="noopener">@dipterix</a>, <a href="https://github.com/dusadrian" target="_blank" rel="noopener">@dusadrian</a>, <a href="https://github.com/georgestagg" target="_blank" rel="noopener">@georgestagg</a>, <a href="https://github.com/goebbe" target="_blank" rel="noopener">@goebbe</a>, <a href="https://github.com/HenrikBengtsson" target="_blank" rel="noopener">@HenrikBengtsson</a>, <a href="https://github.com/jeroen" target="_blank" rel="noopener">@jeroen</a>, <a href="https://github.com/jgf5013" target="_blank" rel="noopener">@jgf5013</a>, <a href="https://github.com/jmbo1190" target="_blank" rel="noopener">@jmbo1190</a>, <a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>, <a href="https://github.com/khusmann" target="_blank" rel="noopener">@khusmann</a>, <a href="https://github.com/kyleGrealis" target="_blank" rel="noopener">@kyleGrealis</a>, <a href="https://github.com/laderast" target="_blank" rel="noopener">@laderast</a>, <a href="https://github.com/lpmi-13" target="_blank" rel="noopener">@lpmi-13</a>, <a href="https://github.com/pepijn-devries" target="_blank" rel="noopener">@pepijn-devries</a>, <a href="https://github.com/seanbirchall" target="_blank" rel="noopener">@seanbirchall</a>, <a href="https://github.com/szcf-weiya" target="_blank" rel="noopener">@szcf-weiya</a>, <a href="https://github.com/timelyportfolio" target="_blank" rel="noopener">@timelyportfolio</a>, <a href="https://github.com/tulaydixon" target="_blank" rel="noopener">@tulaydixon</a>, <a href="https://github.com/WardBrian" target="_blank" rel="noopener">@WardBrian</a>, and <a href="https://github.com/WillemSleegers" target="_blank" rel="noopener">@WillemSleegers</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/bricks.jpg" length="612027" type="image/jpeg" />
    </item>
    <item>
      <title>Ask more of your dashboard with querychat and ggsql</title>
      <link>https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/</link>
      <pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/</guid>
      <dc:creator>Carson Sievert</dc:creator><description><![CDATA[<p>I&rsquo;m super excited to announce that <a href="https://posit-dev.github.io/querychat/" target="_blank" rel="noopener">querychat</a> now supports <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release">ggsql</a>. As a result, querychat users can now go beyond predetermined dashboard views and ask the LLM for bespoke visualizations on the fly. With this addition, querychat now comes with three pre-built tools that LLMs can use in response to user questions:</p>
<ol>
<li><strong>Visualize:</strong> execute <code>ggsql</code> queries, rendered as visualizations in the chat.</li>
<li><strong>Query:</strong> execute SQL queries, rendered as tables/text in the chat.</li>
<li><strong>Filter:</strong> execute filter SQL queries on predetermined views, allowing the LLM to reactively drill into the data in response to user questions.</li>
</ol>
<p>Equipped with these tools, your dashboard can offer a balance between &ldquo;curated insights&rdquo; and &ldquo;explorable data&rdquo; &mdash; surfacing interesting trends and summaries upfront, but also letting users ask follow-up questions that go beyond the predetermined views. The video below gives you a feel for this workflow in action<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Note how the conversation starts off by filtering the predetermined views, but then progresses into more general exploration.</p>
<script src="https://fast.wistia.com/player.js" async></script>
<script src="https://fast.wistia.com/embed/at01coepkh.js" async type="module"></script>
<style>wistia-player[media-id='at01coepkh']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/at01coepkh/swatch'); display: block; filter: blur(5px); padding-top:58.76%; }</style>
<p><wistia-player media-id="at01coepkh" aspect="1.701851851851852"></wistia-player></p>
<p>Crucially, every one of <code>querychat</code>&rsquo;s tools works by executing SQL (or <code>ggsql</code>) &mdash; never arbitrary code. Every query is also fully visible and reproducible: users can inspect the code behind any response, and copy it to run independently. In the next version of <code>querychat</code>, you can also expect to see a rich export capability, allowing you to save the insights you care about in a format you care about (e.g. a Quarto dashboard, Jupyter notebook, or R Markdown file).</p>
<p>Restricting <code>querychat</code>&rsquo;s code execution capability to SQL (or <code>ggsql</code>) is an intentional design choice. We can guarantee that querychat does not run arbitrary R or python code, making it viable for production environments where security and control are required. As a result, <code>querychat</code> may not be as capable as a general coding assistant like <a href="https://posit-dev.github.io/assistant" target="_blank" rel="noopener">Posit Assistant</a>, but you generally don&rsquo;t want production apps that let users execute arbitrary code. By focusing on SQL execution, <code>querychat</code> can maintain a strong security posture while still enabling useful exploration of data via natural language.</p>
<h2 id="get-started">Get started
</h2>
<p>Getting started is as easy as pointing <code>QueryChat()</code> at your data source and getting set up with an LLM. Although we recommend frontier models for an ideal experience, open-weight models have recently become <a href="https://simonpcouch.com/blog/2026-04-16-local-agents-2/" target="_blank" rel="noopener">quite capable</a>, and can be a great way to get started without needing API access or incurring costs.</p>
<p>For this article, I&rsquo;m using <a href="https://lmstudio.ai/" target="_blank" rel="noopener">LMStudio</a> to run <code>google/gemma-4-26b-a4b</code> locally on my fairly standard MacBook Pro. If you prefer a different LLM or provider, refer to the <a href="https://posit-dev.github.io/querychat/py/models.html" target="_blank" rel="noopener">docs</a> (<a href="https://posit-dev.github.io/querychat/r/articles/models.html" target="_blank" rel="noopener">R</a>) to see how to configure.</p>
<p>Once you&rsquo;ve picked an LLM, next step is ensuring both <code>querychat</code> and <code>ggsql</code> are installed. Here we&rsquo;ll also use the <code>palmerpenguins</code> data set to keep examples lightweight and self-contained, but you can point <code>querychat</code> at anything from a data frame to a <a href="https://posit-dev.github.io/querychat/py/data-sources.html" target="_blank" rel="noopener">remote database connection</a> (<a href="https://posit-dev.github.io/querychat/r/articles/data-sources.html" target="_blank" rel="noopener">R</a>).</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Python</a></li>
<li><a href="#tabset-1-2">R</a></li>
</ul>
<div id="tabset-1-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;querychat[viz]&#34;</span> palmerpenguins</span></span></code></pre></div></div>
</div>
<div id="tabset-1-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;querychat&#34;</span><span class="p">,</span> <span class="s">&#34;ggsql&#34;</span><span class="p">,</span> <span class="s">&#34;palmerpenguins&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
</div>
</div>
<h2 id="basic-usage">Basic usage
</h2>
<p>Once installed, you can get a basic chat UI using the code below. We&rsquo;ll include just the <code>query</code> and <code>visualize</code> tools here since the <code>filter</code> tool is designed to work in the context of a larger dashboard, which we&rsquo;ll cover in the next section.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">Python</a></li>
<li><a href="#tabset-2-2">R</a></li>
</ul>
<div id="tabset-2-1">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-2">
  <div class="code-with-filename-label" id="code-filename-2"><span class="font-mono text-sm">app.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span> 
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;query&#34;</span><span class="p">,</span> <span class="s2">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">.</span><span class="n">ui</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">page_opts</span><span class="p">(</span><span class="n">fillable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div></div>
<p>With <code>app.py</code> saved locally and your LLM running, start the app with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">shiny run app.py</span></span></code></pre></div></div>
</div>
<div id="tabset-2-2">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-4">
  <div class="code-with-filename-label" id="code-filename-4"><span class="font-mono text-sm">app.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">palmerpenguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">tools</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;query&#34;</span><span class="p">,</span> <span class="s">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="n">bslib</span><span class="o">::</span><span class="nf">page_fillable</span><span class="p">(</span><span class="n">qc</span><span class="o">$</span><span class="nf">ui</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
<p>With <code>app.R</code> saved locally and your LLM running, start the app with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">runApp</span><span class="p">(</span><span class="s">&#34;app.R&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>Upon opening the app, the LLM greets us with a welcome message tailored to the data source we&rsquo;ve provided:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-chat.png" alt="Basic QueryChat interface to the Palmer Penguins dataset" class="shadow rounded" />
<h3 id="query-tool">Query tool
</h3>
<p>For questions addressable via numerical summaries, the LLM requests the <code>query</code> tool with relevant SQL. The LLM may also choose to collapse details of the tool call, especially when it decides to weave the results within its actual response. That said, full details can always be inspected by clicking on the &ldquo;Query Data&rdquo; tool call display:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-query.png" alt="Query tool in action, showing the SQL query and the resulting summary in the chat response" class="shadow rounded" />
<h3 id="visualize-tool">Visualize tool
</h3>
<p>For questions that are better suited to a visual response, the LLM requests the <code>visualize</code> tool with relevant ggsql. In addition to rendering the plot inline for the user to see, that same plot is also provided to the LLM so that it can interpret the result:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-viz.png" alt="Visualize tool in action, showing the ggsql query and the resulting chart in the chat response" class="shadow rounded" />
<p>A couple noteworthy things about this experience:</p>
<ul>
<li>The footer includes options to view the underlying ggsql query, download the plot as an image, and expand the display to full screen.</li>
<li>Plots are always shown by default, but can be collapsed by clicking the header.</li>
</ul>
<p>This hopefully gives you a taste for how easy it is to get set up with a basic <code>querychat</code> UI and start asking questions of your data.
You may, however, be wondering how <code>querychat</code> is able to produce correct queries and how the code execution actually works. Let&rsquo;s dive into those details next.</p>
<h2 id="how-does-it-work">How does it work?
</h2>
<h3 id="execution">Execution
</h3>
<p>One important thing to understand about <code>querychat</code> is that the LLM itself is not handling the SQL execution &ndash; <code>querychat</code> is. Where that execution ultimately happens depends on what data source you&rsquo;re providing. If you&rsquo;re pointing <code>querychat</code> at an in-memory data frame, the execution happens in-process using DuckDB. If you&rsquo;re pointing it at a database connection, the execution happens against that database. In either case, the LLM is generating queries based on its understanding of the data schema and any additional context you&rsquo;ve provided, not the actual data. This separation is what allows <code>querychat</code> to maintain a strong security posture, scale to large data, and deliver a good user experience.</p>
<h3 id="schema-discovery">Schema discovery
</h3>
<p>To elaborate on what &ldquo;understanding of the data schema&rdquo; actually means: when you point <code>querychat</code> at a data source, it automatically extracts column names, types, numerical ranges, and categorical values. This information is included in the system prompt for the LLM, so it has a clear picture of what the data looks like and can generate accurate queries without needing to see the actual data. For many datasets, this is enough to get good results out of the box.</p>
<h3 id="additional-context">Additional context
</h3>
<p>For more complex datasets or domain-specific questions, you can also provide additional context through a plain-text data description. <code>querychat</code> now also automatically picks up on <a href="https://www.snowflake.com/en/developers/guides/snowflake-semantic-view/" target="_blank" rel="noopener">Snowflake Semantic Models</a> when connected to a Snowflake database, giving the LLM access to authoritative definitions of business logic and metrics without any manual configuration. We hope to add more integrations to other semantic layer formats in the future.</p>
<p>To learn more, <code>querychat</code>&rsquo;s website has more details on <a href="https://posit-dev.github.io/querychat/py/data-sources.html" target="_blank" rel="noopener">data sources</a> (<a href="https://posit-dev.github.io/querychat/r/articles/data-sources.html" target="_blank" rel="noopener">R</a>), <a href="https://posit-dev.github.io/querychat/py/context.html" target="_blank" rel="noopener">providing context</a> (<a href="https://posit-dev.github.io/querychat/r/articles/context.html" target="_blank" rel="noopener">R</a>), and <a href="https://posit-dev.github.io/querychat/py/tools.html" target="_blank" rel="noopener">tool execution</a> (<a href="https://posit-dev.github.io/querychat/r/articles/tools.html" target="_blank" rel="noopener">R</a>).</p>
<h2 id="chat-driven-dashboards">Chat-driven dashboards
</h2>
<p>A chat interface alone likely isn&rsquo;t the experience you want to ship to end users &mdash; the better pattern is to surface interesting findings first, then let users explore beyond them. This is where <code>querychat</code> begins to really shine: the chat interface we already covered can easily be embedded inside of a larger app that includes other outputs &ndash; plots, tables, value boxes, etc. <code>querychat</code> comes with another tool designed for this use case &ndash; the filter tool &ndash; allowing the LLM to effectively drill down into relevant sections of the data in the dashboard in response to user questions.</p>
<p>The key integration point is <code>df()</code>, a reactive value that reflects the current state of the data after any filtering applied by the LLM. Use <code>df()</code> as the data source for your plots, tables, and value boxes, and they&rsquo;ll automatically update in response to user questions in the chat.</p>
<style>
style + .panel-tabset .highlight .chroma {
  max-height: 500px;
  overflow-y: auto !important;
}
</style>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">Python</a></li>
<li><a href="#tabset-3-2">R</a></li>
</ul>
<div id="tabset-3-1">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-6">
  <div class="code-with-filename-label" id="code-filename-6"><span class="font-mono text-sm">app.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">,</span> <span class="s2">&#34;query&#34;</span><span class="p">,</span> <span class="s2">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">.</span><span class="n">sidebar</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span><span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;binoculars&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Penguins&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">count</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;fingerprint&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Species&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">species</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()[</span><span class="s2">&#34;species&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">nunique</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;gauge-high&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Avg Body Mass&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">avg_mass</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()[</span><span class="s1">&#39;body_mass_g&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">g&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Bill Dimensions&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.plot</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">scatter</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="kn">import</span> <span class="nn">plotnine</span> <span class="k">as</span> <span class="nn">p9</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">p9</span><span class="o">.</span><span class="n">ggplot</span><span class="p">(</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">(),</span> <span class="n">p9</span><span class="o">.</span><span class="n">aes</span><span class="p">(</span><span class="s2">&#34;bill_length_mm&#34;</span><span class="p">,</span> <span class="s2">&#34;bill_depth_mm&#34;</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;species&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="o">+</span> <span class="n">p9</span><span class="o">.</span><span class="n">geom_point</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Measurements&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.data_frame</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">page_opts</span><span class="p">(</span><span class="n">fillable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-3-2">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-7">
  <div class="code-with-filename-label" id="code-filename-7"><span class="font-mono text-sm">app.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">DT</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggplot2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">palmerpenguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">tools</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">,</span> <span class="s">&#34;query&#34;</span><span class="p">,</span> <span class="s">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">sidebar</span> <span class="o">=</span> <span class="n">qc</span><span class="o">$</span><span class="nf">sidebar</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">fill</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Penguins&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;count&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;binoculars&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Species&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;species&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;fingerprint&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Avg Body Mass&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;avg_mass&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;speedometer&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span><span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Bill Dimensions&#34;</span><span class="p">),</span> <span class="nf">plotOutput</span><span class="p">(</span><span class="s">&#34;scatter&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span><span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Measurements&#34;</span><span class="p">),</span> <span class="nf">DTOutput</span><span class="p">(</span><span class="s">&#34;table&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">qc_vals</span> <span class="o">&lt;-</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="n">df</span> <span class="o">&lt;-</span> <span class="n">qc_vals</span><span class="o">$</span><span class="n">df</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">count</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">nrow</span><span class="p">(</span><span class="nf">df</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">species</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="nf">unique</span><span class="p">(</span><span class="nf">df</span><span class="p">()</span><span class="o">$</span><span class="n">species</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">avg_mass</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">paste0</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">mean</span><span class="p">(</span><span class="nf">df</span><span class="p">()</span><span class="o">$</span><span class="n">body_mass_g</span><span class="p">,</span> <span class="n">na.rm</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)),</span> <span class="s">&#34;g&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">scatter</span> <span class="o">&lt;-</span> <span class="nf">renderPlot</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">ggplot</span><span class="p">(</span><span class="nf">df</span><span class="p">(),</span> <span class="nf">aes</span><span class="p">(</span><span class="n">bill_length_mm</span><span class="p">,</span> <span class="n">bill_depth_mm</span><span class="p">,</span> <span class="n">color</span> <span class="o">=</span> <span class="n">species</span><span class="p">))</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">geom_point</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">table</span> <span class="o">&lt;-</span> <span class="nf">renderDT</span><span class="p">(</span><span class="nf">df</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/dashboard.png" alt="QueryChat embedded in a dashboard with value boxes, a scatter plot, and a data table — all reactively driven by the chat's filter state" class="shadow rounded" />
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Python users: Shiny v Streamlit/Dash/Gradio</span>
</div>
<div class="callout-body">
<p><code>querychat</code> also supports <code>streamlit</code>, <code>dash</code>, and <code>gradio</code>. That said, the reactive <code>df()</code> pattern shown above &mdash; where the LLM&rsquo;s filter state automatically drives every view in the app &mdash; is what Shiny&rsquo;s reactive programming model was designed for. In other frameworks, keeping the chat&rsquo;s state in sync with multiple outputs typically requires more manual wiring.</p>
</div>
</div>
<h2 id="custom-tools">Custom tools
</h2>
<p><code>querychat</code> comes with three built-in tools, but you can also easily add your own custom tools. This is possible thanks to the extensible foundation provided by <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener">chatlas</a> (<a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>). These packages also make it quite easy to implement tools &ndash; all you really need is a Python / R function that performs some operation. You can also fully customize the display shown, thanks to their rich support for <a href="https://posit-dev.github.io/chatlas/tool-calling/displays.html" target="_blank" rel="noopener">tool call displays</a> (<a href="https://posit-dev.github.io/shinychat/r/articles/tool-ui.html" target="_blank" rel="noopener">R</a>).</p>
<p>To give you a sense of what other capabilities are possible, you could start out as simple as querying <a href="https://posit-dev.github.io/chatlas/get-started/tools.html" target="_blank" rel="noopener">real-time weather information</a>, but also get as sophisticated as a <a href="https://posit-dev.github.io/chatlas/misc/RAG.html#dynamic-retrieval" target="_blank" rel="noopener">RAG-like knowledge retrieval agent</a>. For example, here&rsquo;s how you&rsquo;d let users ask whether weather conditions might relate to trends in your data:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">Python</a></li>
<li><a href="#tabset-4-2">R</a></li>
</ul>
<div id="tabset-4-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatLMStudio</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span> <span class="o">=</span> <span class="n">ChatLMStudio</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_current_weather</span><span class="p">(</span><span class="n">lat</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">lng</span><span class="p">:</span> <span class="nb">float</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the current weather for a location.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat_lng</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;latitude=</span><span class="si">{</span><span class="n">lat</span><span class="si">}</span><span class="s2">&amp;longitude=</span><span class="si">{</span><span class="n">lng</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;https://api.open-meteo.com/v1/forecast?</span><span class="si">{</span><span class="n">lat_lng</span><span class="si">}</span><span class="s2">&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&#34;current&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">get_current_weather</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="n">chat_client</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-4-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span> <span class="o">&lt;-</span> <span class="nf">chat_lmstudio</span><span class="p">(</span><span class="n">model</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">get_current_weather</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lng</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">lat_lng</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;latitude=&#34;</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="s">&#34;&amp;longitude=&#34;</span><span class="p">,</span> <span class="n">lng</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;https://api.open-meteo.com/v1/forecast?&#34;</span><span class="p">,</span> <span class="n">lat_lng</span><span class="p">,</span> <span class="s">&#34;&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">response</span> <span class="o">&lt;-</span> <span class="n">httr</span><span class="o">::</span><span class="nf">GET</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">httr</span><span class="o">::</span><span class="nf">content</span><span class="p">(</span><span class="n">response</span><span class="p">)</span><span class="o">$</span><span class="n">current</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">get_current_weather</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Get the current weather for a location&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Latitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">lng</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Longitude&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">palmerpenguins</span><span class="o">::</span><span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="n">chat_client</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>Underneath the hood, <code>querychat</code>&rsquo;s built-in tools use this same foundation. The &ldquo;magic&rdquo; of these tools really breaks down to three key insights:</p>
<ol>
<li>LLMs are very good at translating natural language into code.</li>
<li>Tool call arguments can be executable code (like SQL or ggsql) rather than just simple values.</li>
<li>Tool calls can <a href="https://shiny.posit.co/py/docs/genai-tools.html#managing-state" target="_blank" rel="noopener">manage reactive state in a Shiny app</a> &mdash; for <code>querychat</code>, that state is the current filter query, but the same pattern could control input controls, tab selection, or other UI state.</li>
</ol>
<h2 id="whats-next">What&rsquo;s next?
</h2>
<p>This release is just the beginning of our vision for (responsibly) exploring data via natural language. We&rsquo;re excited to see what you build with it, and we&rsquo;re already thinking about the next set of features to add. Some things on our near-term roadmap:</p>
<ul>
<li>Multiple tables.</li>
<li>Reproducible takeaway artifacts, like <a href="https://quarto.org/docs/dashboards/" target="_blank" rel="noopener">Quarto dashboards</a></li>
<li>Support for more semantic layer solutions beyond Snowflake, so more users can give the LLM authoritative definitions of their business logic.</li>
</ul>
<h2 id="learn-more">Learn more
</h2>
<ul>
<li><a href="https://posit-dev.github.io/querychat/py/" target="_blank" rel="noopener">querychat documentation</a> (<a href="https://posit-dev.github.io/querychat/r/" target="_blank" rel="noopener">R</a>) &mdash; full guides on data sources, context, tools, and deployment</li>
<li><a href="https://ggsql.org" target="_blank" rel="noopener">ggsql</a> &mdash; the grammar of graphics for SQL that powers querychat&rsquo;s visualizations</li>
<li><a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener">chatlas</a> (<a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>) &mdash; the underlying LLM tool-calling libraries, useful for building custom tools</li>
<li><a href="https://posit-dev.github.io/shinychat/py/" target="_blank" rel="noopener">shinychat</a> (<a href="https://posit-dev.github.io/shinychat/r/" target="_blank" rel="noopener">R</a>) &mdash; the chat UI component that querychat builds on</li>
<li><a href="https://shiny.posit.co/py/" target="_blank" rel="noopener">Shiny</a> (<a href="https://shiny.posit.co/r/" target="_blank" rel="noopener">R</a>) &mdash; the web framework powering querychat apps</li>
<li><a href="https://github.com/posit-dev/querychat" target="_blank" rel="noopener">Source on GitHub</a> &mdash; issues, discussions, and contributions welcome</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gist.github.com/cpsievert/ca0d7e4637fdf994671c9d9fc90cd89f" target="_blank" rel="noopener">Source code</a> is available for reference, though you&rsquo;ll need your own Snowflake account and credentials to run it.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/featured.png" length="454060" type="image/png" />
    </item>
    <item>
      <title>dbplyr 2.6.0</title>
      <link>https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/</link>
      <pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<p>I&rsquo;m pleased to announce that <a href="https://dbplyr.tidyverse.org" target="_blank" rel="noopener">dbplyr 2.6.0</a> is now on CRAN.
dbplyr is the database backend for <a href="https://dplyr.tidyverse.org" target="_blank" rel="noopener">dplyr</a>: it automatically translates the dplyr code you&rsquo;d write for a data frame into SQL so you can run the same pipeline against tables in a remote database.
You can install it with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;dbplyr&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This is another release that has benefited from Claude Code&rsquo;s help, allowing me to rip through a large number of smaller issues (~50!) where the correct solution is easily verified.
And those time savings allowed me to tackle some more challenging and more impactful features, such as new ADBC and JDBC backends and a new <code>sql_dialect()</code> generic that finally separates <em>how you connect to a database</em> from <em>which SQL dialect to use</em>.
The release also includes new translations, a couple of useful helpers, and a substantial cleanup of long-standing deprecations.
This post covers the highlights for everyday users in the first half, then dives into the changes that matter for backend developers at the end.
Read the full list of changes in the <a href="https://github.com/tidyverse/dbplyr/releases/tag/v2.6.0" target="_blank" rel="noopener">release notes</a>.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dbplyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">,</span> <span class="n">warn.conflicts</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="lifecycle-changes">Lifecycle changes
</h2>
<p>A handful of things you may have been using have been deprecated or removed.
The biggest ones to know about:</p>
<ul>
<li>
<p><strong><code>do()</code> is deprecated.</strong> It was the only piece of dplyr 0.x syntax that dbplyr still supported, and it never fit cleanly into SQL.
If you have code that uses it, the simplest path is to <code>collect()</code> first and then use your favourite tidyverse tools on the resulting data frame.</p>
</li>
<li>
<p><strong><code>str_like(ignore_case = TRUE)</code> is deprecated</strong> in favour of the new <code>str_ilike()</code> (thanks to <a href="https://github.com/edward-burn" target="_blank" rel="noopener">@edward-burn</a>).
This brings the dbplyr translation in line with the recent updates to <a href="https://stringr.tidyverse.org" target="_blank" rel="noopener">stringr</a>.</p>
</li>
<li>
<p><strong>The <code>cte</code> argument</strong> of <code>collect()</code>, <code>compute()</code>, <code>show_query()</code>, <code>remote_query()</code>, and <code>db_sql_render()</code> is deprecated.
Pass <code>sql_options = sql_options(cte = TRUE)</code> instead.
This consolidates a growing pile of one-off flags into a single options object that we can extend without tweaking every render function.</p>
</li>
</ul>
<p>A few deprecations that have been warning for multiple years are now defunct: passing <code>...</code> to <code>across()</code>/<code>if_all()</code>/<code>if_any()</code>, using <code>by = character()</code> to perform a cross join (use <code>cross_join()</code> instead), and calling <code>compute(temporary = FALSE)</code> without a <code>name</code>.
A few defunct functions/arguments have been removed entirely, including <code>group_by(add = )</code>, <code>partial_eval(var)</code>, and <code>src_sql()</code>.</p>
<p>If you&rsquo;re a backend developer, there&rsquo;s a longer list of changes that affect you &mdash; see the <a href="#notes-for-backend-developers">Notes for backend developers</a> at the end of this post.</p>
<h2 id="new-backends-adbc-and-jdbc">New backends: ADBC and JDBC
</h2>
<p>dbplyr already worked with ODBC (thanks to {<a href="https://odbc.r-dbi.org" target="_blank" rel="noopener">odbc</a>}).
With 2.6.0, you can now connect through two more transport layers:</p>
<ul>
<li>
<p><strong><a href="https://arrow.apache.org/adbc/" target="_blank" rel="noopener">ADBC</a></strong> (Arrow Database Connectivity) is a relatively new project from the Apache Arrow community.
It moves data between R and the database as Arrow buffers, which avoids the per-row serialization overhead of older protocols and can be much (much!) faster.
dbplyr supports ADBC connections through {<a href="https://github.com/r-dbi/adbi" target="_blank" rel="noopener">adbi</a>}.</p>
</li>
<li>
<p><strong><a href="https://en.wikipedia.org/wiki/Java_Database_Connectivity" target="_blank" rel="noopener">JDBC</a></strong> (Java Database Connectivity) is the standard database protocol on the JVM, with high-quality drivers for nearly every database under the sun.
dbplyr supports JDBC connections through {<a href="https://github.com/s-u/RJDBC" target="_blank" rel="noopener">RJDBC</a>}.</p>
</li>
</ul>
<p>The reason these new backends slot in so cleanly is a new generic, <code>sql_dialect()</code>.
Until now, dbplyr picked SQL translations based on the connection class itself, which meant every new transport had to either masquerade as an existing connection or carry its own copy of every translation.
(Or as with {odbc}, hack the database name into the object class.)
That&rsquo;s why ADBC and JDBC support was hard to add in the past.</p>
<p><code>sql_dialect()</code> separates the two concerns: a connection&rsquo;s job is to know how to talk to the server and fetch data, and the dialect&rsquo;s job is to know how to generate the right SQL.
For example, when you connect over JDBC or ADBC to Postgres, dbplyr now uses the same translations it would use over ODBC or via <a href="https://rpostgres.r-dbi.org" target="_blank" rel="noopener">RPostgres</a>.
This simplified a bunch of dbplyr code and made it very easy to add the new ADBC and JDBC backends.
Finally, if the default dialect isn&rsquo;t quite right, you can override it with <code>with_dialect()</code>.</p>
<h2 id="new-translations">New translations
</h2>
<p>A few translations are now available.</p>
<p><code>bind_queries()</code> is the dbplyr equivalent of <code>dplyr::bind_rows()</code>: it combines several lazy queries into one using <code>UNION ALL</code>, aligning columns as needed.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">con</span> <span class="o">&lt;-</span> <span class="n">DBI</span><span class="o">::</span><span class="nf">dbConnect</span><span class="p">(</span><span class="n">RSQLite</span><span class="o">::</span><span class="nf">SQLite</span><span class="p">(),</span> <span class="s">&#34;:memory:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db1</span> <span class="o">&lt;-</span> <span class="nf">copy_to</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">tibble</span><span class="o">::</span><span class="nf">tibble</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">),</span> <span class="s">&#34;db1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db2</span> <span class="o">&lt;-</span> <span class="nf">copy_to</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">tibble</span><span class="o">::</span><span class="nf">tibble</span><span class="p">(</span><span class="n">y</span> <span class="o">=</span> <span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="m">4</span><span class="o">:</span><span class="m">6</span><span class="p">),</span> <span class="s">&#34;db2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">bind_queries</span><span class="p">(</span><span class="n">db1</span><span class="p">,</span> <span class="n">db2</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nf">show_query</span><span class="p">()</span></span></span></code></pre></div></div>
<pre><code>&lt;SQL&gt;
SELECT *, NULL AS `y`
FROM `db1`

UNION ALL

SELECT `x`, `y`
FROM `db2`
</code></pre>
<p><code>filter_out()</code> (from <a href="https://opensource.posit.co/blog/2026-02-04_dplyr-1-2-0">dplyr 1.2.0</a>) is now translated.
It&rsquo;s the opposite of <code>filter()</code>, dropping rows where the conditions are true, rather than keeping those rows.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">&lt;-</span> <span class="nf">copy_to</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">tibble</span><span class="o">::</span><span class="nf">tibble</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="kc">NA</span><span class="p">),</span> <span class="n">g</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">)),</span> <span class="s">&#34;db3&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">filter_out</span><span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="m">2</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A query:  ?? x 2
# Database: sqlite 3.52.0 [:memory:]
      x     g
  &lt;dbl&gt; &lt;dbl&gt;
1     1     1
2    NA     2
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="m">2</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A query:  ?? x 2
# Database: sqlite 3.52.0 [:memory:]
      x     g
  &lt;dbl&gt; &lt;dbl&gt;
1     2     1
</code></pre>
<p><code>anyNA()</code> is now translated, in the same way as <code>any(is.na(x))</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">summarise</span><span class="p">(</span><span class="n">missing</span> <span class="o">=</span> <span class="nf">anyNA</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="n">.by</span> <span class="o">=</span> <span class="n">g</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nf">show_query</span><span class="p">()</span></span></span></code></pre></div></div>
<pre><code>&lt;SQL&gt;
SELECT `g`, MAX((`x` IS NULL)) AS `missing`
FROM `db3`
GROUP BY `g`
</code></pre>
<p>And <code>%notin%</code> (new in R 4.6.0) is now translated to <code>NOT IN</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">x</span> <span class="o">%notin%</span> <span class="nf">c</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">))</span> <span class="o">|&gt;</span> <span class="nf">show_query</span><span class="p">()</span></span></span></code></pre></div></div>
<pre><code>&lt;SQL&gt;
SELECT *
FROM `db3`
WHERE (`x` NOT IN (1.0, 2.0))
</code></pre>
<p>Three other changes are worth knowing about:</p>
<ul>
<li>
<p><strong><code>last_sql()</code></strong> returns the SQL of the most recent query dbplyr generated.
This is mostly useful when debugging a problem that surfaces inside <code>collect()</code> or <code>compute()</code>, when you don&rsquo;t have a convenient handle on the query to call <code>show_query()</code> on.</p>
</li>
<li>
<p><strong><code>copy = &quot;inline&quot;</code></strong> is now an option for joins, set operations, and row operations.
When you join a local data frame to a remote table, dbplyr normally copies it to a temporary table on the server.
With <code>copy = &quot;inline&quot;</code>, it inlines the data into the SQL directly using <code>copy_inline()</code>, which is handy when you can&rsquo;t (or don&rsquo;t want to) create a temporary table.</p>
</li>
<li>
<p>Thanks to <a href="https://github.com/shearerpmm" target="_blank" rel="noopener">@shearerpmm</a>, this release also gains a translation layer for IBM DB2.
The translation includes <code>paste()</code>/<code>paste0()</code> (using <code>||</code>), DB2-specific casts, <code>runif()</code>, a comprehensive set of string and date functions, the <a href="https://clock.r-lib.org" target="_blank" rel="noopener">clock</a> helpers, and statistical aggregates.</p>
</li>
</ul>
<h2 id="notes-for-backend-developers">Notes for backend developers
</h2>
<p>If you maintain a dbplyr backend, several things have changed and you may need to make some adjustments.
Hopefully none of this is a surprise, since I&rsquo;ve already filed PRs to all CRAN packages that needed changes 😀.</p>
<p>Most importantly, the new <code>sql_dialect()</code> generic separates out the connection details from SQL generation.
You should create a <code>sql_dialect()</code> method for your connection that returns a <code>new_sql_dialect()</code> object.
<code>new_sql_dialect()</code> lets you customize the full surface of SQL generation, including how identifiers are quoted (which allows tests to look much closer to real SQL).
Once you have that object, all the <code>sql_</code> generics can dispatch on it, rather than on the connection object.</p>
<p>There are two new extension points for customization:</p>
<ul>
<li>
<p><code>db_table_drop_if_exists()</code> is a new generic that lets backends customize how tables are dropped when <code>overwrite = TRUE</code>.
It was added to support Oracle, which needs a slightly different <code>DROP TABLE</code> incantation.</p>
</li>
<li>
<p><code>sql_set_op_method()</code> is a new generic that lets set operations (<code>union()</code>, <code>intersect()</code>, <code>setdiff()</code>) customize the SQL keyword they generate.
Useful when a backend needs <code>UNION DISTINCT</code> instead of <code>UNION</code>, or <code>MINUS</code> instead of <code>EXCEPT</code>.</p>
</li>
</ul>
<p>And two deprecations:</p>
<ul>
<li>
<p><code>sql_expr_matches()</code> is deprecated. Provide <code>is_distinct_from()</code> and <code>is_not_distinct_from()</code> translations instead. These power joins with <code>na_matches = &quot;na&quot;</code> and the new <code>filter_out()</code> translation.</p>
</li>
<li>
<p><code>as.sql()</code> is deprecated as part of a major internal refactor of how <code>sql()</code> and <code>ident()</code> are used. You can generally replace it with <code>as_table_path()</code> if used to refer to a table, or <code>sql()</code> if you want to indicate it&rsquo;s raw SQL.</p>
</li>
</ul>
<p>I&rsquo;ve also overhauled the exported tools for generating SQL strings. The newly exported <code>sql_glue()</code> (implicit dialect, for use inside <code>sql_translation()</code>) and <code>sql_glue2()</code> (explicit dialect, for everywhere else) provide a glue-style syntax for building SQL strings. They replace the now superseded <code>build_sql()</code>, <code>sql_expr()</code>, and <code>sql_call2()</code>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/featured.jpg" length="281420" type="image/jpeg" />
    </item>
    <item>
      <title>tidyclust 0.3.0</title>
      <link>https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/</link>
      <pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/</guid>
      <dc:creator>Emil Hvitfeldt</dc:creator><description><![CDATA[<p>I&rsquo;m thrilled to announce that <a href="https://tidyclust.tidymodels.org/" target="_blank" rel="noopener">tidyclust 0.3.0</a> is now on CRAN.
tidyclust allows you to fit, interact, and evaluate unsupervised clustering models inside the tidymodels framework.
You can install it with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;tidyclust&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This release brings three new families of clustering models,
closer alignment with the rest of tidymodels,
and a number of smaller improvements.
This post covers the highlights of new models and engines,
tighter integration with tidymodels,
and updated parallel processing support.
You can read the full list of changes in the <a href="https://tidyclust.tidymodels.org/news/index.html#tidyclust-030" target="_blank" rel="noopener">release notes</a>.</p>
<p>To get started,
load the tidymodels and tidyclust packages:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidymodels</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidyclust</span><span class="p">)</span></span></span></code></pre></div></div>
<p>For the examples, we will be using the <code>penguins</code> data set.
We are imputing missing values and normalizing the predictors since distance and density-based methods are sensitive to scale and missingness.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">data</span><span class="p">(</span><span class="n">penguins</span><span class="p">,</span> <span class="n">package</span> <span class="o">=</span> <span class="s">&#34;modeldata&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rec_spec</span> <span class="o">&lt;-</span> <span class="nf">recipe</span><span class="p">(</span><span class="o">~</span> <span class="n">bill_length_mm</span> <span class="o">+</span> <span class="n">bill_depth_mm</span> <span class="o">+</span> <span class="n">flipper_length_mm</span> <span class="o">+</span> <span class="n">body_mass_g</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                   <span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">step_impute_mean</span><span class="p">(</span><span class="nf">all_numeric_predictors</span><span class="p">())</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">step_normalize</span><span class="p">(</span><span class="nf">all_numeric_predictors</span><span class="p">())</span></span></span></code></pre></div></div>
<h2 id="new-models-and-engines">New models and engines
</h2>
<p>Until now, tidyclust supported partition-based methods (<code>k_means()</code>) and hierarchical clustering (<code>hier_clust()</code>).
This release expands that considerably with three new model specifications.</p>
<p><a href="https://tidyclust.tidymodels.org/reference/db_clust.html" target="_blank" rel="noopener"><code>db_clust()</code></a> fits density-based clustering models (DBSCAN),
with engines for both <code>&quot;dbscan&quot;</code> and <code>&quot;hdbscan&quot;</code>.
Density-based methods are well suited to finding clusters of arbitrary shape and identifying outliers as noise.</p>
<p>To use tidyclust,
we first make a specification for the clustering method,
combine it with the recipe, and fit the workflow as usual.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db_spec</span> <span class="o">&lt;-</span> <span class="nf">db_clust</span><span class="p">(</span><span class="n">radius</span> <span class="o">=</span> <span class="m">0.8</span><span class="p">,</span> <span class="n">min_points</span> <span class="o">=</span> <span class="m">5</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;dbscan&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db_fit</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">rec_spec</span><span class="p">,</span> <span class="n">db_spec</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">fit</span><span class="p">(</span><span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db_fit</span></span></span></code></pre></div></div>
<pre><code>══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: db_clust()

── Preprocessor ────────────────────────────────────────────────────────────────
2 Recipe Steps

• step_impute_mean()
• step_normalize()

── Model ───────────────────────────────────────────────────────────────────────
DBSCAN clustering for 344 objects.
Parameters: eps = 0.8, minPts = 5
Using euclidean distances and borderpoints = TRUE
The clustering contains 2 cluster(s) and 5 noise points.

  0   1   2 
  5 218 121 

Available fields: cluster, eps, minPts, metric, borderPoints
</code></pre>
<p>Notice the <code>Outlier</code> level: points in low-density regions aren&rsquo;t forced into a cluster.
This is also our first new engine where you don&rsquo;t specify how many clusters to find up front.</p>
<p><a href="https://tidyclust.tidymodels.org/reference/gm_clust.html" target="_blank" rel="noopener"><code>gm_clust()</code></a> fits Gaussian mixture models via the <code>&quot;mclust&quot;</code> engine.
Rather than assigning each observation to a single cluster,
mixture models describe the data as a combination of Gaussian components.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">gm_spec</span> <span class="o">&lt;-</span> <span class="nf">gm_clust</span><span class="p">(</span><span class="n">num_clusters</span> <span class="o">=</span> <span class="m">3</span><span class="p">)</span> <span class="o">|&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;mclust&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gm_fit</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">rec_spec</span><span class="p">,</span> <span class="n">gm_spec</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">fit</span><span class="p">(</span><span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">tidy</span><span class="p">(</span><span class="n">gm_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 3 × 8
  component  size proportion variance mean.bill_length_mm mean.bill_depth_mm
      &lt;int&gt; &lt;int&gt;      &lt;dbl&gt;    &lt;dbl&gt;               &lt;dbl&gt;              &lt;dbl&gt;
1         1   160      0.458    0.288              -0.892              0.524
2         2    61      0.184    0.288               0.938              0.834
3         3   123      0.358    0.288               0.658             -1.10 
# ℹ 2 more variables: mean.flipper_length_mm &lt;dbl&gt;, mean.body_mass_g &lt;dbl&gt;
</code></pre>
<p>Each row summarizes one of the fitted Gaussian components:
its size, mixing proportion, and the per-predictor means that describe where it sits.</p>
<p><a href="https://tidyclust.tidymodels.org/reference/mean_shift.html" target="_blank" rel="noopener"><code>mean_shift()</code></a> fits mean shift models,
which iteratively shift observations toward regions of high density and determine the number of clusters automatically.
Engines <code>&quot;LPCM&quot;</code> and <code>&quot;meanShiftR&quot;</code> are supported.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ms_spec</span> <span class="o">&lt;-</span> <span class="nf">mean_shift</span><span class="p">(</span><span class="n">bandwidth</span> <span class="o">=</span> <span class="m">0.2</span><span class="p">)</span> <span class="o">|&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;LPCM&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ms_fit</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">rec_spec</span><span class="p">,</span> <span class="n">ms_spec</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">fit</span><span class="p">(</span><span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">extract_cluster_assignment</span><span class="p">(</span><span class="n">ms_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 344 × 1
   .cluster 
   &lt;fct&gt;    
 1 Cluster_1
 2 Cluster_1
 3 Cluster_1
 4 Cluster_1
 5 Cluster_1
 6 Cluster_1
 7 Cluster_1
 8 Cluster_1
 9 Cluster_1
10 Cluster_1
# ℹ 334 more rows
</code></pre>
<p>This returns a cluster label for each observation,
with the number of clusters discovered automatically rather than set in advance like how it was in <code>db_clust()</code>.</p>
<p>Each of these models comes with its respective <code>dials</code> parameters for tuning.</p>
<h2 id="closer-alignment-with-tidymodels">Closer alignment with tidymodels
</h2>
<p>A major theme of this release is removing the seams between tidyclust and the rest of tidymodels.
Tidyclust was previously written as a slightly modified copy of the internals of the tune package.
We took some time to align the internals of tune such that we could use the same code paths for tidyclust and tune.
This means that we have a smaller maintenance burden and new features in tune should be more easily available in tidyclust.</p>
<p>The biggest change is that <code>finalize_model_tidyclust()</code> and <code>finalize_workflow_tidyclust()</code> are now deprecated.
The corresponding functions in tune <code>tune::finalize_model()</code> and <code>tune::finalize_workflow()</code> now support <code>cluster_spec</code> objects natively,
so there is no longer a need for tidyclust-specific variants.</p>
<p>Parallel processing in <code>tune_cluster()</code> now supports the <a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai</a> package in addition to <code>future</code>.
This matches the parallelism approach used across tidymodels.
As part of this change,
the <code>foreach</code> package is no longer supported for parallel processing&mdash;use <code>future</code> or <code>mirai</code> instead.
The <code>.config</code> column produced by <code>tune_cluster()</code> has also changed from the <code>Preprocessor{num}_Model{num}</code> pattern to <code>pre{num}_mod{num}_post{num}</code> to align with updates in tune.</p>
<p>A few other improvements worth calling out:</p>
<ul>
<li>A new <a href="https://tidyclust.tidymodels.org/articles/tidyclust.html" target="_blank" rel="noopener">&ldquo;Getting started with tidyclust&rdquo;</a> vignette.</li>
<li><code>butcher</code> support for <code>cluster_fit</code> objects, so you can strip training data and environment references from fitted models before saving them.</li>
<li><code>extract_cluster_assignment()</code>, <code>extract_centroids()</code>, and <code>predict()</code> gain a <code>labels</code> argument for supplying custom cluster labels.</li>
<li><code>hier_clust()</code> gains a <code>dist_fun</code> argument for specifying a custom distance function.</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to everyone who has contributed issues, pull requests, and discussion since the last release!
<a href="https://github.com/brendad8" target="_blank" rel="noopener">@brendad8</a>, <a href="https://github.com/davidrsch" target="_blank" rel="noopener">@davidrsch</a>, <a href="https://github.com/dnldelarosa" target="_blank" rel="noopener">@dnldelarosa</a>, <a href="https://github.com/EmilHvitfeldt" target="_blank" rel="noopener">@EmilHvitfeldt</a>, <a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>, <a href="https://github.com/kbodwin" target="_blank" rel="noopener">@kbodwin</a>, <a href="https://github.com/lgaborini" target="_blank" rel="noopener">@lgaborini</a>, <a href="https://github.com/topepo" target="_blank" rel="noopener">@topepo</a>, and <a href="https://github.com/Wander03" target="_blank" rel="noopener">@Wander03</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/thumbnail.jpg" length="233780" type="image/jpeg" />
    </item>
    <item>
      <title>A brief and biased history of Posit data science agents</title>
      <link>https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/</link>
      <pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/</guid>
      <dc:creator>Joe Cheng</dc:creator><description><![CDATA[<p>For the last 18 months, many of us at Posit have been focused on building AI agents designed to help RStudio and Positron users get their work done faster and more effectively. And we truly believe that we&rsquo;ve succeeded! Our latest agent, <a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, is extremely good and getting better with each passing week.</p>
<p>But Posit Assistant was not our first agent, and it&rsquo;s not likely to be our last. The proliferation of agents coming from Posit has started causing serious confusion. This post hopes to clear that up by providing a chronology of the agents we&rsquo;ve created, our rationale for their design and architecture, and what you need to know about them today (if anything).</p>
<h2 id="tldr">TL;DR
</h2>
<p>Posit Assistant is our newest, most powerful agent for data science. It builds on what we learned from creating our previous agents: Positron Assistant and Databot.</p>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Description</th>
          <th>Status</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong><a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a></strong></td>
          <td>Coding agent available in RStudio and Positron (and more)</td>
          <td>Active</td>
      </tr>
      <tr>
          <td><strong><a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Positron Assistant</a></strong></td>
          <td>Positron&rsquo;s coding agent</td>
          <td>Superseded by Posit Assistant, still available until Q3 2026</td>
      </tr>
      <tr>
          <td><strong><a href="https://positron.posit.co/databot.html" target="_blank" rel="noopener">Databot</a></strong></td>
          <td>EDA agent in Positron</td>
          <td>Superseded by Posit Assistant</td>
      </tr>
  </tbody>
</table>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">What about Posit AI?</span>
</div>
<div class="callout-body">
<p><a href="https://posit.ai/" target="_blank" rel="noopener">Posit AI</a>, not to be confused with Posit Assistant or Positron Assistant, is a model subscription service that can be used to power Posit Assistant.</p>
</div>
</div>
<h2 id="positron-assistant">Positron Assistant
</h2>
<ul>
<li><strong>Strengths:</strong> Time to market; connects to a variety of providers; tight IDE integration</li>
<li><strong>Weaknesses:</strong> Legacy agent architecture; not available in RStudio IDE</li>
<li><strong>Status:</strong> Succeeded by Posit Assistant (as of June 2026)</li>
</ul>
<p>This was the first &ldquo;off the shelf&rdquo; data science agent we ever shipped, and is available exclusively in Positron. It was forked from GitHub Copilot, and like Copilot, you can ask it general coding questions, have it write code, and have it explain existing code to you.</p>
<p>While the <a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Positron Assistant</a> experience is largely the same as Copilot, there are two major differences. The first is that it can access Positron-specific features like running code in an interactive R or Python session, inspecting variables, and generating/inspecting plots. These features make it much more useful for data work.</p>
<p>The second major difference is the ability to connect to other LLM providers than the Copilot service, right out of the box. At this time, Positron Assistant can connect to Anthropic and OpenAI (with API key), AWS Bedrock, Snowflake Cortex, GitHub Copilot, Microsoft Foundry, Google Gemini, <a href="https://posit.ai/" target="_blank" rel="noopener">Posit AI</a>, and even custom (OpenAI-compatible) providers. When running in a properly configured Posit Workbench installation, these can often be seamlessly authenticated.</p>
<p>GitHub Copilot is tightly integrated with VS Code, and Positron Assistant is similarly well integrated with Positron. You can start an &ldquo;inline chat&rdquo; right at your editor cursor and ask Positron Assistant to fix or explain errors with one click. These were major benefits to starting with GitHub Copilot as a base.</p>
<p>Unfortunately, there are serious downsides to the GitHub Copilot heritage as well. The architecture and core APIs of Copilot were designed to be extended <a href="https://code.visualstudio.com/api/extension-guides/ai/chat" target="_blank" rel="noopener">in ways</a> that didn&rsquo;t end up being very popular, while tool calling felt grafted on and cache control nonexistent. The net effect was a lot of abstraction and complexity between the user prompt and what actually went into the LLM request, making it difficult for us to get the best performance out of the LLM. And users noticed this, saying that Positron Assistant was generally effective but also duller than other agents, including Databot and Claude Code, even when using the same underlying model.</p>
<p>Another big downside to GitHub Copilot was that its chat UI was more austere and harder to customize than what we could do on our own. Any changes we made were asking for technical debt, multiplied by the incredibly fast pace of upstream changes that we need to regularly merge.</p>
<p>Overall, the Positron Assistant project gave us what we needed at the time: a competitive, full-featured, IDE-integrated, agentic coding assistant. But for data work, we felt like we knew how to do better in a number of ways, including agent performance and UI/UX.</p>
<h2 id="databot">Databot
</h2>
<ul>
<li><strong>Strengths:</strong> Effective agent harness; well tailored to exploration tasks</li>
<li><strong>Weaknesses:</strong> Not general purpose enough; not available in RStudio IDE</li>
<li><strong>Status:</strong> Succeeded by Posit Assistant (as of June 2026)</li>
</ul>
<p>Databot was conceived in December 2024, in response to a specific question: what would happen if we took a frontier model (Claude 3.5 Sonnet), seeded it with simple instructions, and gave it unfettered access to the most powerful tool we could think of (a live R or Python session)?</p>
<p>At the time, it seemed hopelessly reckless to give so much power to an LLM, and we ran the first prototype with real trepidation! Fortunately, this approach is more effective and significantly less dangerous in practice than in theory. In a world before Claude Code, Databot felt like absolute magic. That magic was compounded by three decisions we made in an effort to make Databot excellent for exploratory data analysis (EDA):</p>
<ol>
<li>The ability not only to execute code but to see the results (including tables and plots)</li>
<li>Keeping the human in the feedback loop by only allowing so many tool calls before stopping to summarize progress and check in with the user</li>
<li>Always providing three to five suggestions of what to do next</li>
</ol>
<p>With a suitably capable LLM, this combination makes data exploration feel like flying.</p>
<p>It took us months to convince ourselves that a tool like this was safe enough to ship to users, and months more to turn the prototype into production-ready software. Since this felt like such an aggressive architecture, we constrained Databot to a narrow remit: it was only for interactively exploring data. After exploration was complete, it could also export a reproducible Quarto report that records both the findings and the methods, but that was pretty much it.</p>
<p>We officially launched Databot on August 28, 2025 with <a href="https://posit.co/blog/introducing-databot" target="_blank" rel="noopener">this blog post</a>. We were still so nervous about the dangers that we published a <a href="https://posit.co/blog/databot-is-not-a-flotation-device" target="_blank" rel="noopener">companion blog post</a> at the same time, telling users how <em>not</em> to use Databot. As models have gotten better, and tens of millions of users have grown accustomed to aggressively agentic AI tools, this second blog post remains technically accurate but perhaps reads a bit alarmist. In practice, agentic AI coding tools are not completely safe, but neither are they necessarily as dangerous as they may theoretically appear. Their usefulness across a wide variety of tasks helps balance this risk.</p>
<p>Databot&rsquo;s crucial weakness turned out not to be that it was too dangerous, but too narrowly scoped. EDA does not happen in a vacuum. It&rsquo;s often done in service of some larger project, and insights derived from EDA typically immediately lead to the creation of a reproducible cleaning script, interactive Shiny app, automated data transformation job, etc. As an EDA-focused agent harness, Databot did not have the tools needed to do those tasks well.</p>
<p>This constrained nature left users feeling frustrated. Databot could be so intelligent, so facile with data exploration, yet so suddenly clumsy the moment you asked it to edit a data cleaning script, and the distinction felt arbitrary. Even if you understood Databot&rsquo;s limitations, it still left you needing to manually perform a &ldquo;hand off&rdquo; between an EDA session in Databot and a Shiny app authoring session in Positron Assistant.</p>
<h3 id="why-did-we-make-two-agents-instead-of-one">Why did we make two agents instead of one?
</h3>
<p>Given the obvious downsides of creating two distinct agents for Positron instead of focusing on one — confusion, disjointed experience, duplication of effort — why did we do it?</p>
<p>First, as stated above, Databot felt not just risky but borderline reckless in how much power we were encouraging the agent to wield. Positron Assistant had very similar behavior to Copilot, so while not totally &ldquo;safe&rdquo;, it felt like a widely understood and accepted set of tradeoffs. It felt reasonable to have one conservative agent that we could tell everyone to use in Positron Assistant, and one aggressive agent for early adopters to play with in Databot.</p>
<p>Second, Positron Assistant seemed easier to build because we could start with so much existing functionality from Copilot. For Databot, we had to build everything from scratch: the agent harness, the chat UI, loading and saving past conversations, everything, and it was hard to know in the beginning how fast we could do it and how successful we would be. Again, it felt right to have one agent that we knew we could ship quickly, and one that might take longer but would potentially give us a better long-term platform.</p>
<p>These reasons made sense to us in early 2025, but they haven&rsquo;t stood the test of time. As Databot gestated, Claude Code showed the world that powerful AI agents had a usefulness-to-risk ratio that far, far exceeded most people&rsquo;s thresholds. And forking Copilot didn&rsquo;t give us nearly the boost in development speed we expected: while it did give Positron Assistant a huge head start, it also saddled Positron Assistant with many unnecessarily complex abstractions and introduced us to the very expensive recurring task of merging the torrent of upstream Copilot changes with our extensive modifications.</p>
<p>A final reason had to do not with risk but with specialization: Databot&rsquo;s <a href="https://posit.co/blog/introducing-databot" target="_blank" rel="noopener">WEAR loop</a> felt distinct from Positron Assistant&rsquo;s generic loop, and this felt like a pretty fundamental UX difference. Once we got serious about combining the two, it turned out not to be so fundamental after all, and for the most part we feel like our next agent succeeds at &ldquo;code-switching&rdquo; as needed.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<ul>
<li><strong>Strengths:</strong> Effective agent harness; great for EDA, coding, and general-purpose agent tasks; integrated into both Positron and RStudio</li>
<li><strong>Weaknesses:</strong> Not designed for highly autonomous or highly parallel agentic work (if that&rsquo;s what you&rsquo;re into)</li>
<li><strong>Status:</strong> Now available in RStudio, in preview in Positron</li>
</ul>
<p>Posit Assistant is our newest data science agent, designed to build on what we liked most about each of the AI agents we&rsquo;ve built and used in the past:</p>
<ul>
<li>A simple agent harness with a small number of powerful and general tools</li>
<li>Prompting, tools, and UI/UX designed for the needs of data scientists</li>
<li>Full access to live R/Python sessions, including variables and plots</li>
<li>Integrated into our data science IDEs</li>
<li>A codebase designed for fast iteration</li>
<li>Extension points based on MCP and skills</li>
</ul>
<p>Crucially, we built Posit Assistant to live in both RStudio (since April 2026) and Positron (since June 2026). Not only did this finally bring an integrated agent to RStudio, where most R users still live today, but it also meant that every ounce of effort we put into improving Posit Assistant helps both RStudio and Positron users.</p>
<p>While Positron Assistant (the older one) grew out of Copilot, Posit Assistant (the new one) grew out of Databot. There was much we liked about Databot, and it didn&rsquo;t take much effort to expand its mission from the narrow task of exploratory data analysis to being a great general-purpose agent.</p>
<p>The big advantage of Positron Assistant in early 2025 turned out to be a big disadvantage by early 2026: being built on GitHub Copilot. While Copilot was arguably one of the two or three best coding agents for most of its existence, by the end of 2025 it was thoroughly outmatched by Claude Code.</p>
<p>At the risk of oversimplifying, the Copilot approach thinks the user should play a major role in deciding what context the underlying LLM gets to see. For example, Copilot and Positron Assistant have UI features that let you specify what files from your project you want to include with your next user prompt. You can also use <code>@-mentions</code> to bring in reference materials (for example, the Shiny VS Code extension lets you mention <code>@shiny</code> to bring in the Shiny docs).</p>
<p>In contrast, Claude Code&rsquo;s approach is to give an LLM access to powerful general-purpose abilities, like reading/writing/editing files and executing arbitrary bash commands, and then trusting the LLM to use them to gather the information it needs. This approach worked well when Claude Code was released in February 2025, and then dramatically improved over the course of 2025. The overall effect was that Claude Code felt significantly smarter and more engaged, even when using the same underlying language model.</p>
<p><strong>The highest compliment I can give to Posit Assistant is this: when exploring data, it feels a lot like Databot, and when coding, it feels a lot like Claude Code.</strong> And where Claude Code is a general-purpose coding agent, Posit Assistant is built specifically for people who work with data, with modes and skills for data tasks like data cleaning, Shiny app creation, and predictive modeling.</p>
<h3 id="why-did-we-call-it-posit-assistant-when-we-already-had-positron-assistant">Why did we call it &ldquo;Posit Assistant&rdquo; when we already had &ldquo;Positron Assistant&rdquo;!?
</h3>
<p>Maybe the most controversial thing about Posit Assistant is its name: Posit Assistant. First, it&rsquo;s quite dull. Second, why isn&rsquo;t it &ldquo;Posit Agent&rdquo;? Third, why would you give it almost-but-not-exactly the same name as the thing it&rsquo;s superseding?</p>
<p>These are all decisions that were made by me, Joe Cheng, and if you think they are bad decisions, then just know the Posit Assistant team agrees with you. (I stand by the name, but only just!)</p>
<p><strong>On dullness:</strong> A natively integrated AI chatbot for an IDE is table stakes, not a distinguishing feature. While we need a name today, I want users to eventually think of Posit Assistant as &ldquo;RStudio&rsquo;s chat pane&rdquo; or &ldquo;Positron&rsquo;s built-in AI&rdquo;, that is, for its identity to be subsumed into the host IDE. We believe most data practitioners will continue to use IDEs even as these models get more capable, and that the &ldquo;I&rdquo; in IDE is where we can offer real value. Instead of trying to build a new, distinct brand, I wanted there to barely be a brand at all.</p>
<p><strong>On &ldquo;Agent&rdquo;:</strong> The term &ldquo;agent&rdquo; is heavily overloaded these days. In the sense that it&rsquo;s been used so far in this article, it means <a href="https://simonwillison.net/2025/May/22/tools-in-a-loop/" target="_blank" rel="noopener">&ldquo;models using tools in a loop,&rdquo;</a> and Posit Assistant definitely fits that definition. But a lot of our customers understand the word to refer to &ldquo;models working highly autonomously for long periods of time&rdquo;, and we really wanted to avoid this connotation. Posit Assistant does have the ability to work autonomously if necessary and appropriate, but much of our harness encourages the model not to work autonomously in situations where it otherwise would.</p>
<p><strong>On Positron Assistant/Posit Assistant:</strong> This one is truly confusing, and the hardest one to defend. My defense is that the confusion is temporary. For most of Positron Assistant&rsquo;s short history, Posit Assistant didn&rsquo;t exist. For most of Posit Assistant&rsquo;s history, Positron Assistant will no longer exist. We&rsquo;re in a moment right now where we&rsquo;re mid-transition, so it&rsquo;s very confusing, but hopefully, that confusion is temporary.</p>
<p>I hope you will find this explanation to be satisfactory, and if not, at least now you know who to blame!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/images/featured.png" length="248470" type="image/png" />
    </item>
    <item>
      <title>RStudio&#39;s Top Feature Requests ... In Positron</title>
      <link>https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/</link>
      <pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/</guid>
      <dc:creator>Jonathan McPherson</dc:creator><description><![CDATA[<p>One of the most fun aspects of developing software in the open is that you don&rsquo;t have to guess what your users want; you can just let them tell you. For over a decade, <a href="https://github.com/rstudio/rstudio/issues" target="_blank" rel="noopener">RStudio&rsquo;s issue tracker</a> has been public, available for anyone to report problems or request enhancements. And you, the community, have delivered, collectively filing thousands of bug reports and feature ideas.</p>
<p>One of the <em>least</em> fun aspects, though, is that you never have time to do most (or even <em>half</em>) of the great ideas that bubble up through the community. RStudio&rsquo;s issue tracker is full of worthy requests that have been unimplemented for years, often because they would require outsized changes to the core system&rsquo;s architecture or behavior.</p>
<p>When we set out to make Positron, RStudio&rsquo;s issue tracker became a gold mine of possibilities. Because Positron has a new architecture and is built on a different platform, many of the things that we&rsquo;d never been able to do in RStudio suddenly became possible.</p>
<p>Today, we take a look at 10 of the most upvoted RStudio feature requests of all time and how those features have been incorporated into Positron. Every one of these 10 requests is on the <a href="https://github.com/rstudio/rstudio/issues?q=is%3Aissue%20state%3Aopen%20sort%3Areactions-%2B1-desc" target="_blank" rel="noopener">front page of the most-upvoted RStudio issues</a> as of this writing.</p>
<h2 id="1-fully-configurable-pane-layouts">#1: Fully configurable pane layouts
</h2>
<blockquote>
<p>Most IDEs have a somewhat more flexible system which allows for arbitrary tab arrangement, often using mouse gestures to edit configuration on the fly.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2879" target="_blank" rel="noopener">2879</a></p>
<p>This has been one of RStudio&rsquo;s most-upvoted requests for years. In Positron, this feature is largely inherited from upstream Code OSS (the open source core of VS Code). It has a very flexible layout system which allows tabs to be rearranged, split, hidden, and more with natural dragging and mouse gestures, or via context menus. Try right-clicking on any tab or divider to show places you can move it:</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/01-pane-layout.png" height="1180" width="898" alt="Customizable pane layout in Positron" />
<p>Read more about it here:</p>
<p><a href="https://code.visualstudio.com/docs/configure/custom-layout" target="_blank" rel="noopener">VS Code Layout Configuration</a></p>
<p>Positron also ships with a variety of layout presets customized for specific tasks. Try the <em>View: Stacked Layout</em> command to make it look more like RStudio, or <em>View: Notebook Layout</em> to focus on a Quarto document or Jupyter Notebook.</p>
<h2 id="2-fixed-size-graphics-device">#2: Fixed size graphics device
</h2>
<blockquote>
<p>It would be excellent if one could set the RStudio graphics device to generate plots as they would be generated by a non-interactive device with fixed dimensions, so that one could do iterative plot-making within the IDE rather than call <code>png()</code>, look at the outputs in another program, and then fiddle with font scaling, etc. and repeat.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/4422" target="_blank" rel="noopener">4422</a></p>
<p>RStudio always draws plots to fit the exact dimensions of its Plots pane, which can make it difficult to iterate on a plot you&rsquo;re preparing for publication.</p>
<p>When we added a Plots pane to Positron, we included a new tool that lets you indicate the dimensions at which you&rsquo;d like the plot to draw. You can have it fit to some common aspect ratios or &ndash; as in the RStudio feature request &ndash; specify the exact size at which you&rsquo;d like the plot to be drawn.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/02-plot-size.png" height="1016" width="1026" alt="The Plots pane with a custom size dialog" />
<p>Positron&rsquo;s Plots pane also features a visual history browser, and it remembers the code used to create the plot so you can jump to it or re-run it.</p>
<h2 id="3-multi-line-statement-support-for-python-code">#3: Multi-line statement support for Python code
</h2>
<blockquote>
<p>It would be really great to have RStudio support of multi-line statements extended to Python code as well.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/9014" target="_blank" rel="noopener">9014</a></p>
<p>One of RStudio&rsquo;s key R features is multi-line statement detection. When you tell RStudio to execute R code at your cursor location, it doesn&rsquo;t just send the current line of code; it gathers all the lines that are part of the statement and sends them together. RStudio doesn&rsquo;t know how to do this for Python, however.</p>
<p>In Positron, Python and R are peers, and we&rsquo;ve implemented multi-line statement detection for Python, too. Placing your cursor anywhere in a Python statement and invoking Run will execute the whole statement.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/03-python-multi-line.png" height="1130" width="1280" alt="A multi-line Python statement" />
<h2 id="4-tree-view-for-browsing-files-and-directories">#4: Tree view for browsing files and directories
</h2>
<blockquote>
<p>Hi, is there any plan for browsing files and directories in tree view mode?</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2552" target="_blank" rel="noopener">2552</a></p>
<p>RStudio&rsquo;s file browser lists only one directory at a time. Positron inherits Code OSS&rsquo;s tree-based explorer, which not only shows files and directories in context but even has <a href="https://code.visualstudio.com/docs/configure/themes#_file-icon-themes" target="_blank" rel="noopener">customizeable icons</a>!</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/04-file-explorer.png" alt="A tree-view file explorer" />
<h2 id="5-view-different-parts-of-a-source-file-at-the-same-time">#5: View different parts of a source file at the same time
</h2>
<blockquote>
<p>In TexShop there’s an option to split source which allows you to see (and edit) multiple areas in a document in one window so that you don’t need to keep scrolling up and down. It would be wonderful to have this as a functionality in the RStudio IDE editor</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2129" target="_blank" rel="noopener">2129</a></p>
<p>In Positron, you can have several editors open at once against the same file. With a file open, try using the <em>Split Right</em> or <em>Split Down</em> commands, which will create a second tab for the file that can be scrolled independently. You can access this via the Command Palette, or via the editor action toolbar:</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/05-split-source-file.png" alt="A single R script open in two different editor tabs" />
<h2 id="6-add-builds-for-windows-on-arm">#6: Add builds for Windows on ARM
</h2>
<blockquote>
<p>RStudio is built for Windows only for Intel (x86) processors. However, there is now a version of Windows 11 available for ARM based processors.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/11977" target="_blank" rel="noopener">11977</a></p>
<p>Microsoft has taken a page from Apple&rsquo;s playbook and started producing its own ARM-based hardware, such as the <a href="https://learn.microsoft.com/en-us/surface/surface-arm-faq" target="_blank" rel="noopener">ARM-based Surface devices</a>, and ARM-based Windows PCs are also now available from third-party manufacturers.</p>
<p>When we built Positron, we designed its native components to be easy to recompile against different CPU architectures. On Windows, Positron includes both x64 and arm64 R computation engines (kernels), so it can run natively with both x64 and arm64 versions of R. Its Python support, likewise, works great on ARM-based PCs.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/06-windows-arm.png" alt="R aarch64 running natively in Positron" />
<h2 id="7-persistent-julia-engine">#7: Persistent Julia engine
</h2>
<blockquote>
<p>It would be useful if we can use Julia in RStudio seamlessly as well as Python.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/1798" target="_blank" rel="noopener">1798</a></p>
<p>One of the main architectural goals of Positron was to make it adaptable to any language. It is a data science platform that supports &ldquo;language extensions&rdquo;. Positron&rsquo;s Python and R support subsystems are included in your Positron download, but are implemented as extensions.</p>
<p>While Posit doesn&rsquo;t currently have the expertise to develop well-rounded support for Julia, that hasn&rsquo;t stopped the community from creating a Positron extension that you can install to add Julia support. When installed, Julia becomes a peer of R and Python in Positron, and connects to Positron&rsquo;s Console, Variables, History, Packages, and Plots panes.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/07-julia-support.png" alt="Positron running a Julia session" />
<p>Find it here: <a href="https://open-vsx.org/extension/ntluong95/positron-julia" target="_blank" rel="noopener">Julia for Positron</a></p>
<p>One of the questions we get a lot is why Positron itself isn&rsquo;t a VS Code extension. This is one of the reasons why: Positron isn&rsquo;t an extension; it <em>has</em> extensions.</p>
<h2 id="8-teachingpresentation-mode">#8: Teaching/presentation mode
</h2>
<blockquote>
<p>Quite a lot of people use RStudio hooked up to a projector to teach others. It would be great if those people had a key combo that could toggle between their current theme settings and some good defaults suitable for teaching/demos. &hellip; Perhaps the ultimate goal would be to have the teaching mode itself configurable.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/4276" target="_blank" rel="noopener">4276</a></p>
<p>In Positron, it is possible to define different &ldquo;profiles&rdquo; that have different themes, font sizes, and other settings, and switch between them from the Command Palette, so it is possible set up a &ldquo;Presentation&rdquo; profile with a high-contrast theme and large text.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/08-presentation-mode.png" alt="Positron's Profile editor showing a Presentation mode" />
<p>Profiles go beyond just layouts and themes. More documentation here:</p>
<p><a href="https://code.visualstudio.com/docs/configure/profiles" target="_blank" rel="noopener">https://code.visualstudio.com/docs/configure/profiles</a></p>
<h2 id="9-fix-columns-or-rows-when-scrolling-data">#9: Fix columns or rows when scrolling data
</h2>
<blockquote>
<p>The data viewer is one of the most used tool of data analysis in RStudio. However, a very useful feature is missing. In horizontal scrolling a big table with lots of column (it&rsquo;s very common to have more columns than one screen can fit), it will be very helpful to fix the row name column so that it&rsquo;s always visible(i.e. always keep it within screen with scrolling). &hellip; More generally, sometimes we also want to fix certain columns so that it remains visible when scrolled horizontally. The row name column is just a special case of this request.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/3463" target="_blank" rel="noopener">3463</a></p>
<p>It&rsquo;s pretty common to have a column of data that represents a key value or name for the observation, or a value to which you wish to compare other values. In Positron, you can pin <em>any</em> column to fix it to the left, so that it is always visible as you scroll the other columns horizontally. Click on the column&rsquo;s action menu (vertical ellipsis) and choose <em>Pin Column</em>.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/09-pin-column.png" alt="Positron's Data Explorer with a column pinned" />
<p>You can also pin rows! Right-click on the row and choose <em>Pin Row</em>.</p>
<h2 id="10-remove-all-objects-in-the-python-environment">#10: Remove all objects in the Python environment
</h2>
<blockquote>
<p>Normally, we can do it easily in R environment by just 1 click but Python doesn&rsquo;t support it.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/8750" target="_blank" rel="noopener">8750</a></p>
<p>It&rsquo;s supported in Positron! With a Python session open in the Console, you can use the trash icon to clear all the objects from the session &ndash; in one click.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/10-python-delete-all.png" alt="The Delete All Objects feature in a Python session" />
<h2 id="-and-a-lot-more">&hellip; And a lot more.
</h2>
<p>Some of our other favorite Positron-only features that don&rsquo;t have a top-upvoted RStudio issue attached:</p>
<ul>
<li><strong>Multiple R sessions</strong>: RStudio runs only one primary R session at a time; any concurrent work needs to be done in a non-interactive background job. Positron supports multiple concurrent interactive R sessions.</li>
<li><strong>Multiple R versions</strong>: RStudio can only work with one R version at a time, and you need to switch externally using a tool like <a href="https://github.com/r-lib/rig" target="_blank" rel="noopener">rig</a>. Positron will let you choose from any R installation on your system and can even associate specific R versions with specific projects.</li>
<li><strong>Crash recovery</strong>: RStudio crashes when R does, but in Positron all you&rsquo;ll lose is the R session itself, which just gets safely restarted.</li>
<li><strong>Remote sessions</strong>: Connect remotely to another computer over SSH and run R sessions inside it, or work with reproducible projects inside <a href="https://containers.dev/" target="_blank" rel="noopener">devcontainers</a>.</li>
</ul>
<p>See <a href="https://positron.posit.co/migrate-rstudio-compare.html" target="_blank" rel="noopener">Comparing RStudio and Positron Features</a> for more.</p>
<h2 id="what-about-rstudio">What about RStudio?
</h2>
<p>While Positron&rsquo;s design has made it more practical to make many of these advancements, the point of this post isn&rsquo;t that you should switch to Positron if you&rsquo;re happy in RStudio. In fact, features are also flowing in the other direction; many of Positron&rsquo;s features are making their way back into RStudio. For example, the latest release of RStudio has:</p>
<ul>
<li>a redesigned data viewer inspired by Positron&rsquo;s data viewer;</li>
<li>code formatting optionally <a href="https://posit-dev.github.io/air/editor-rstudio.html" target="_blank" rel="noopener">powered by air</a>, Positron&rsquo;s R formatter;</li>
<li>warning/error styling in the R console inspired by Positron&rsquo;s Console; and, of course</li>
<li><a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, a platform-agnostic data analysis assistant that works in both IDEs.</li>
</ul>
<p>If you use RStudio and are interested in trying Positron, a good place to start is our <a href="https://positron.posit.co/migrate-rstudio.html" target="_blank" rel="noopener">Migrating from RStudio guide</a>.</p>
<p>Thank you for the creativity, ideas, and support you&rsquo;ve shown our IDEs over the years. Keep it coming!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/header.png" length="111055" type="image/png" />
    </item>
    <item>
      <title>Posit Assistant is specialized for data work</title>
      <link>https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/</link>
      <pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/</guid>
      <dc:creator>Sara Altman</dc:creator><description><![CDATA[<p>We often get questions about how Posit Assistant compares to other coding agents like Claude Code. If you&rsquo;re already using Posit Assistant with a Claude model, are they really that different? At a high level, Claude Code is an extremely useful general-purpose coding agent, but <strong>we designed Posit Assistant specifically for people who work with data.</strong> This post covers three specific differences:</p>
<ol>
<li>Posit Assistant has out-of-the-box access to your R or Python session.</li>
<li>Posit Assistant can display plots in the chat, leading to easier interpretation for you and the agent.</li>
<li>Posit Assistant has specialized interaction modes, skills, and prompting for data analysis.</li>
</ol>
<p>You can also see a full demo of these features in this video:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/7GI6-4J0AXA"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">What's the difference between Posit Assistant, Positron Assistant, and Posit AI?</span>
</div>
<div class="callout-body">
<p>This post talks about <a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, Posit&rsquo;s latest coding agent available in RStudio and Positron. <a href="https://posit.co/products/ai" target="_blank" rel="noopener">Posit AI</a> is a model subscription service that can power Posit Assistant. <a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Posit<em>ron</em> Assistant</a> is a coding agent built into Positron. We plan for Posit Assistant to supersede Positron Assistant in Q3 2026.</p>
</div>
</div>
<h3 id="coding-agents-vs-chat-apps">Coding agents vs. chat apps
</h3>
<p>Claude Code and Posit Assistant are both <em>coding agents</em>. An <em>agent</em> is a <a href="https://simonwillison.net/2025/Sep/18/agents/" target="_blank" rel="noopener">&ldquo;model running tools in a loop,&rdquo;</a> meaning an agent can see your environment, take actions in that environment (e.g., run code), and iterate based on the outcome of those actions. These abilities make agents vastly more useful for coding than chat apps like ChatGPT.</p>
<p>Chat apps can&rsquo;t see your environment or take any actions in your environment. You have to copy-and-paste code into your session, run it yourself, and paste any errors or output back. This both makes the experience more frustrating and increases the probability of errors, because the model is writing code that operates on data and files that it doesn&rsquo;t actually have access to.</p>
<p>If you haven&rsquo;t tried a coding agent before and are still relying on chat apps for coding assistance, we recommend trying one out.</p>
<h2 id="built-in-access-to-your-rpython-session">Built-in access to your R/Python session
</h2>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/pa-cc-session-access.png"
      alt="Screenshot comparing Posit Assistant and Claude Code&rsquo;s access to an R session. Posit Assistant can see session objects directly, while Claude Code must write and run scripts."  title="Posit Assistant has access to your R session out of the box." 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Posit Assistant has access to your R session out of the box.</figcaption>
  </figure></div>
</p>
<p><strong>The first major difference is that Posit Assistant has built-in access to your R or Python session.</strong> This means that Posit Assistant, without any additional configuration, can see your R or Python objects and run code directly in your active session.</p>
<p>Claude Code lacks this ability out of the box. It can write R code to files and run scripts, but it can&rsquo;t directly access data you&rsquo;ve loaded or run R code interactively.</p>
<p>If you ask a coding agent without session access to, for example, filter a tibble <code>df</code> you have in your environment, it will write R code to a script and then run that script with the <code>Rscript</code> command. This works, but has a couple of downsides:</p>
<ol>
<li>Before running the script, the agent can&rsquo;t actually tell that <code>df</code> exists. If it doesn&rsquo;t exist, it will have to wait for an error message from the script.</li>
<li>You might not want to create and run scripts for every bit of code.</li>
<li>It leaves you and the agent on different footing: you have direct access to your session, but the agent doesn&rsquo;t. This can make errors more likely and the general experience more frustrating.</li>
</ol>
<p>It is possible to give agents like Claude Code access to your R or Python session by using an MCP server. In the walkthrough video, we show how to do this using the <a href="https://github.com/posit-dev/btw" target="_blank" rel="noopener">btw package</a>, giving Claude Code the ability to access our R session and run R code directly.</p>
<p>Even with an MCP server, however, it may be difficult to see the code being run on your behalf. For some tasks, this might not matter, but if you&rsquo;re analyzing data it can be important to understand the analysis in detail. For data analysis tasks, Posit Assistant is designed for auditability and transparency. The code is visible in the tool call UI with syntax highlighting and styling, so you can easily audit what&rsquo;s happening to your data at each step.</p>
<p>See this comparison in the video: <a href="https://youtu.be/7GI6-4J0AXA?t=220" target="_blank" rel="noopener">Claude Code</a> | <a href="https://youtu.be/7GI6-4J0AXA?t=424" target="_blank" rel="noopener">Posit Assistant</a></p>
<h2 id="data-visualization">Data visualization
</h2>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/pa-plots.png"
      alt="Screenshot of Posit Assistant in RStudio displaying a plot directly in the chat panel, as well as in the plots pane, alongside the code that generated it."  title="Posit Assistant displays plots directly in the chat, making them visible to both you and the model." 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Posit Assistant displays plots directly in the chat, making them visible to both you and the model.</figcaption>
  </figure></div>
</p>
<p>Claude Code can write code to create plots, but can&rsquo;t display them natively in the terminal and will need an alternative way to show them to you (e.g., opening a file in Preview). Even for the model itself to see the plot, it has to save the plot to a PNG file and then read that file.</p>
<p><strong>Because Posit Assistant runs code in your console, plots show up in the RStudio or Positron plot pane, as well as directly in the chat panel. This makes it easier for you to quickly inspect and analyze the plot and iterate if needed.</strong> Posit Assistant will also automatically see the plot image and typically interpret it or assess whether it is correct, which makes it very useful for EDA and data analysis.</p>
<p>This ability for you and the agent to both see the same plot is important for auditability. There&rsquo;s evidence that even the most advanced models <a href="https://simonpcouch.github.io/bluffbench/" target="_blank" rel="noopener">sometimes misinterpret plots that contradict their expectations</a>, and so having you and the agent looking at and interpreting the same plot makes it easier to catch mistakes.</p>
<p>See this comparison in the video: <a href="https://youtu.be/7GI6-4J0AXA?t=560" target="_blank" rel="noopener">Claude Code</a> | <a href="https://youtu.be/7GI6-4J0AXA?t=703" target="_blank" rel="noopener">Posit Assistant</a></p>
<h2 id="specialized-data-analysis-capabilities">Specialized data analysis capabilities
</h2>
<p>Session access and plotting affordances both directly support data analysis, but Posit Assistant also has interaction modes, prompting, and skills specifically designed for it.</p>
<p>Posit Assistant is both a general-purpose coding agent and a specialized data analysis agent, and it behaves slightly differently depending on your task. During data exploration, it only runs a few bits of code at a time, then summarizes what it found and suggests next steps. Because the purpose of data analysis, especially exploration, is for you to learn about the data, the entire process breaks down if the model runs ahead with analysis you can&rsquo;t keep up with. <strong>Posit Assistant&rsquo;s approach to co-analysis with the user is designed to support your understanding of the data rather than hinder it.</strong></p>
<figure>
<img src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/pa-data-analysis.gif" alt="GIF of Posit Assistant carrying out some data analysis on the likeliness words data. Posit Assistant does three rounds of tool calls, summarizes what it found, and provides some suggestions." width="600px" />
<figcaption>Posit Assistant explores data iteratively, running a few bits of code at a time before summarizing and suggesting next steps.</figcaption>
</figure>
<p>Claude Code excels at writing code, but is generally oriented toward writing that code to files, executing it, and reviewing it, rather than exploring data step-by-step with the user.</p>
<p><strong>Posit Assistant also has specialized prompting, skills, and tools that promote sound data analysis.</strong> These include skills for making Shiny apps and Quarto reports, a <a href="https://opensource.posit.co/blog/2026-05-08_ai-newsletter/" target="_blank" rel="noopener">data cleaning mode</a>, and prompting about rigorous statistical and modeling practices.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/data-cleaning-mode.png"
      alt="Screenshot of Posit Assistant&rsquo;s data cleaning mode, showing a tabbed interface with issues like &lsquo;Sparse early/late years&rsquo;. The assistant presents a table of row counts by year range, explains the imbalance, and offers actionable choices: keep all years, filter to 2011–2022, or filter to 2011–2019."  title="Posit Assistant in data cleaning mode, surfacing questions to the user." 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Posit Assistant in data cleaning mode, surfacing questions to the user.</figcaption>
  </figure></div>
</p>
<p>See this comparison in the video: <a href="https://youtu.be/7GI6-4J0AXA?t=990" target="_blank" rel="noopener">Claude Code</a> | <a href="https://youtu.be/7GI6-4J0AXA?t=1133" target="_blank" rel="noopener">Posit Assistant</a></p>
<h2 id="posit-assistant-is-designed-for-data-work">Posit Assistant is designed for data work
</h2>
<p>Claude Code, Codex, and other coding agents are very useful tools. They excel at general-purpose coding tasks, and you might want to use them for work that benefits from running multiple agents in parallel, or because you want to take advantage of your existing subscription plans.</p>
<p>However, we made Posit Assistant because we think the millions of data scientists, researchers, and analysts who rely on R and Python should have an excellent agent specifically designed for them and their needs. Those needs include data analysis and visualization, but also package development, Shiny app creation, and other coding-focused tasks in the data science ecosystem.</p>
<p>Learn more about Posit Assistant: <a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">https://posit-dev.github.io/assistant/</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/featured.png" length="134058" type="image/png" />
    </item>
    <item>
      <title>Positron June Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/</link>
      <pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<p><a href="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/" target="_blank" rel="noopener">Last release</a> we introduced <a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a>, our unified, data-science-focused approach to AI assistance. This release continues that work, and we want to give you advance notice that the older Positron Assistant will be deprecated in the next release. If you are still using Positron Assistant, we encourage you to migrate to Posit Assistant now.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/posit-assistant.gif" data-fig-align="center" data-fig-alt="Posit Assistant in Positron, Posit&#39;s unified data-science AI assistant, which is replacing the older Positron Assistant." />
<div class="callout callout-important" role="note" aria-label="Important">
<div class="callout-header">
<span class="callout-title">Important</span>
</div>
<div class="callout-body">
<p>During this migration period, you will likely want to keep both the old <a href="positron://settings/positron.assistant.enable"><code>positron.assistant.enable</code></a> setting and the new <a href="positron://settings/assistant.enabled"><code>assistant.enabled</code></a> set to true for best functionality, but do be aware that the older setting is in the process of being deprecated.</p>
</div>
</div>
<p>Posit Assistant supports the same broad set of providers as Positron Assistant, along with the Posit AI model provider and new experimental support for Google Vertex. Learn more about the differences between the new Posit Assistant and the older Positron Assistant:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/Y9P2nlFXKnQ"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<p>You may also be interested in <a href="https://youtu.be/7GI6-4J0AXA" target="_blank" rel="noopener">comparing Posit Assistant and Claude Code</a>.</p>
<p>New in Posit Assistant this release, you can use the Posit AI model provider for Next Edit Suggestions, which propose your likely next change as you edit. You can also access the <strong>Configure Language Model Providers</strong> item in the accounts menu. You can <a href="https://github.com/posit-dev/assistant-feedback" target="_blank" rel="noopener">ask a question, report a bug, or request a new feature</a> for Posit Assistant separately from Positron now, but don&rsquo;t worry too much about where to send your feedback; we will help route it to the right place!</p>
<h2 id="packages-pane-improvements">Packages pane improvements
</h2>
<p>We introduced the <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages</a> pane last release, and this release makes it more informative and flexible. A <strong>Show Help</strong> button and context menu entry on every package take you straight to its documentation in the <a href="https://positron.posit.co/help-pane" target="_blank" rel="noopener">Help pane</a>. You can now combine category filters as an intersection, so you can narrow the list to, for example, packages that are both attached and outdated. A new <strong>Item Size</strong> toggle lets you switch between a compact row view and a richer card view that surfaces package descriptions and other metadata.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/packages-pane.gif" data-fig-align="center" data-fig-alt="The Positron Packages pane showing the attached category filter, the Item Size toggle switching between compact rows and a card view with package descriptions, and how to update a package." />
<p>For R users, the new <a href="positron://settings/packages.r.installer"><code>packages.r.installer</code></a> setting controls whether installs, updates, and removals use pak, base R, or an automatic choice. The setting that controls the pane has been renamed to <a href="positron://settings/packages.enabled"><code>packages.enabled</code></a>; the previous <code>positron.packages.enable</code> setting is deprecated but still honored.</p>
<h2 id="inline-output-for-quarto">Inline output for Quarto
</h2>
<p><a href="https://positron.posit.co/quarto-inline-output" target="_blank" rel="noopener">Inline output for <code>.qmd</code> documents</a> was one of Positron&rsquo;s most-requested features ever, and it is now out of preview. A new toolbar button in Quarto documents gives you one-click access to running cells, managing inline output, and showing the console.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/inline-output.gif" data-fig-align="center" data-fig-alt="A Quarto document in Positron showing inline output beneath an executed code cell, with the new toolbar button for managing output." />
<p>Inserting code into a Quarto document from the History pane now automatically wraps it in cell markup when you drop it into a prose region. You can opt into inline output with the <a href="positron://settings/positron.quarto.inlineOutput.enabled"><code>positron.quarto.inlineOutput.enabled</code></a> setting.</p>
<h2 id="faster-startup">Faster startup
</h2>
<p>We continue to invest in performance improvements, and Positron starts up even faster in this release. Positron now caches previously discovered system interpreters, dramatically speeding up startup in new folders and projects; you can control this with the new <a href="positron://settings/interpreters.discoveryCache.enabled"><code>interpreters.discoveryCache.enabled</code></a> setting. On Windows, we fixed multi-minute startup delays by skipping slow <code>PATH</code> discovery by default, governed by the new <a href="positron://settings/positron.r.interpreters.pathDiscoveryMode"><code>positron.r.interpreters.pathDiscoveryMode</code></a> setting. We also fixed an occasional hang at &ldquo;Preparing&rdquo; when starting Positron after updating to a new version.</p>
<h2 id="a-more-customizable-interface">A more customizable interface
</h2>
<p>This release gives you finer control over Positron&rsquo;s chrome so you can tailor the interface to your workflow. Several layout settings that previously weren&rsquo;t always honored now work as expected:</p>
<ul>
<li><a href="positron://settings/workbench.topActionBar.visible"><code>workbench.topActionBar.visible</code></a> reliably hides and shows the top action bar at runtime</li>
<li><a href="positron://settings/workbench.secondarySideBar.defaultVisibility"><code>workbench.secondarySideBar.defaultVisibility</code></a> is respected when you set it explicitly</li>
<li><a href="positron://settings/workbench.secondarySideBar.showLabel"><code>workbench.secondarySideBar.showLabel</code></a> lets Secondary Side Bar items render as compact icons</li>
</ul>
<p>You can also enable <a href="positron://settings/window.commandCenter"><code>window.commandCenter</code></a> to bring the command center into the title bar. Learn more about some of our own team members&rsquo; favorite customizations:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/QIYyeuZ_ISY"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<h2 id="r-language-intelligence">R language intelligence
</h2>
<p>Positron&rsquo;s R language support now understands symbols within a file. You can rename a local symbol and have every use of it updated at once, and Go to Definition and Find References now work for local symbols, so you can jump to where a variable is defined (such as via <code>&lt;-</code> or a function parameter) or see everywhere it is used within a function. Renaming symbols across files is not yet supported, but it is coming soon.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>We have some exciting milestones planned for our July release! The <a href="https://positron.posit.co/positron-notebook-editor.html" target="_blank" rel="noopener">new notebook editor for <code>.ipynb</code> files</a>, the <a href="https://positron.posit.co/packages-pane.html" target="_blank" rel="noopener">Packages pane</a>, and <a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a> will all come out of preview to general availability. We are thrilled for these features to be ready for your production workflows, and we hope you will give them a try and <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">share your feedback</a>.</li>
<li>We&rsquo;re still prototyping first-class SQL editing and execution in Positron, including support for visualizations with <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release/" target="_blank" rel="noopener">ggsql</a>. We&rsquo;ll share more as this takes shape, and in the meantime, <a href="https://github.com/posit-dev/positron/issues/7233" target="_blank" rel="noopener">let us know</a> your thoughts and current pain points!</li>
<li>If you will be at the Databricks Data + AI Summit next week, <a href="https://posit.co/events/databricks-data-ai-summit-2026" target="_blank" rel="noopener">join us</a> to learn about using Positron and Workbench within the Databricks platform.</li>
<li>We are looking forward to posit::conf(2026) in September, where our team will have several sessions on Positron. The <a href="https://posit.co/blog/posit-conf-2026-agenda-breakdown" target="_blank" rel="noopener">conference program</a> was released last week and we are pretty excited about everything happening! <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/featured.png" length="73388" type="image/png" />
    </item>
    <item>
      <title>Introducing Toolbars: Supercharge your Cards and Inputs</title>
      <link>https://opensource.posit.co/blog/2026-05-26_introducing-toolbars/</link>
      <pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-26_introducing-toolbars/</guid>
      <dc:creator>Liz Nelson</dc:creator><description><![CDATA[<p>You&rsquo;re building a dashboard. It&rsquo;s looking pretty good. But every time you talk to your users, someone comes up with another must-have filter or button. Maybe you&rsquo;re even adding an AI chat feature for the first time and realizing that you&rsquo;ve got less space than it takes to write &ldquo;this text box is smaller than I thought&rdquo; to fit all those buttons and selects.</p>
<p>Good news! Toolbars are here to save you.</p>
<p>Their small size and low-profile aesthetic is perfect for keeping your favorite filters and buttons compact and legible. They&rsquo;re modular and they work in some of your favorite existing components.
Seeing is believing, so let&rsquo;s jump in!</p>
<p>We&rsquo;re going to provide some examples of classic spots where you might want a toolbar.</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Short on time?</span>
</div>
<div class="callout-body">
<p>Jump straight to the functionality you&rsquo;re most interested in:</p>
<ul>
<li><a href="#toolbars-in-card-headers--footers">Toolbars in Card Headers &amp; Footers</a></li>
<li><a href="#attaching-controls-to-inputs">Toolbars in Input Labels</a></li>
<li><a href="#complex-text-inputs-with-toolbars">Complex Text Inputs with Toolbars</a></li>
</ul>
</div>
</div>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Getting toolbars</span>
</div>
<div class="callout-body">
<p>Toolbars ship in <strong>bslib 0.11.0</strong> (R) and <strong>py-shiny 1.6.0</strong> (Python).</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">R</a></li>
<li><a href="#tabset-1-2">Python</a></li>
</ul>
<div id="tabset-1-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;bslib&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-1-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install --upgrade shiny</span></span></code></pre></div></div>
</div>
</div>
</div>
</div>
<h2 id="our-example">Our Example
</h2>
<p>We&rsquo;re going to build a version of this app piece by piece:</p>
<script src="https://fast.wistia.com/player.js" async></script>
<script src="https://fast.wistia.com/embed/4o5yqcn2tc.js" async type="module"></script>
<style>wistia-player[media-id='4o5yqcn2tc']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/4o5yqcn2tc/swatch'); display: block; filter: blur(5px); padding-top:67.81%; }</style>
<p><wistia-player media-id="4o5yqcn2tc" aspect="1.4746543778801844"></wistia-player></p>
<h2 id="setup-code">Setup Code
</h2>
<p>Let&rsquo;s get you set up with a basic app with some cards. We&rsquo;ll use this canvas as a starting point for our toolbar components.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">R</a></li>
<li><a href="#tabset-2-2">Python</a></li>
</ul>
<div id="tabset-2-1">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-2">
  <div class="code-with-filename-label" id="code-filename-2"><span class="font-mono text-sm">app_1.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h2</span><span class="p">(</span><span class="s">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Toolbar in header &amp; label&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder text.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Toolbar in header, footer, label&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;More placeholder text.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Footer content here.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for outputs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Placeholder</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-2-2">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-3">
  <div class="code-with-filename-label" id="code-filename-3"><span class="font-mono text-sm">app_1.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">Session</span><span class="p">,</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">h2</span><span class="p">(</span><span class="s2">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Toolbar in header &amp; label&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder text.&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Toolbar in header, footer, label&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;More placeholder text.&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_footer</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Footer content here.&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for outputs&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">:</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">output</span><span class="p">:</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>A toolbar on its own is pretty simple. All you have to do is:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">R</a></li>
<li><a href="#tabset-3-2">Python</a></li>
</ul>
<div id="tabset-3-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">toolbar</span><span class="p">(</span><span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-3-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span><span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>That&rsquo;s just an empty container. The real fun is when we start adding our toolbar inputs and connecting them to card contents.</p>
<h2 id="toolbars-in-card-headers--footers">Toolbars in Card Headers &amp; Footers
</h2>
<p>Toolbars are great for card headers and footers because they provide a clear way to associate a set of controls with a given subset of the app. For example, you can see below that the header toolbar provides controls that clearly correspond to the contents of the card.</p>
<figure>
<img src="https://opensource.posit.co/blog/2026-05-26_introducing-toolbars/card1.png" alt="Sales Data card with a header toolbar holding a region select toolbar dropdown and two toolbar buttons (swap and save), above a sales table." />
<figcaption aria-hidden="true">Sales Data card with a header toolbar holding a region select toolbar dropdown and two toolbar buttons (swap and save), above a sales table.</figcaption>
</figure>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">R</a></li>
<li><a href="#tabset-4-2">Python</a></li>
</ul>
<div id="tabset-4-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;region_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;floppy-disk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-7">
  <div class="code-with-filename-label" id="code-filename-7"><span class="font-mono text-sm">app_2.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h2</span><span class="p">(</span><span class="s">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;region_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;floppy-disk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">tableOutput</span><span class="p">(</span><span class="s">&#34;sales_table&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Toolbar in header, footer, label&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;More placeholder text.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Footer content here.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for outputs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle save button click</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">save_btn</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Change icon to checkmark and show notification</span>
</span></span><span class="line"><span class="cl">    <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Saved&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">session</span> <span class="o">=</span> <span class="n">session</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">showNotification</span><span class="p">(</span><span class="s">&#34;Saving&#34;</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">  <span class="n">sales_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">data.frame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">Product</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Region</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Sales</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1200</span><span class="p">,</span> <span class="m">850</span><span class="p">,</span> <span class="m">1450</span><span class="p">,</span> <span class="m">920</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">stringsAsFactors</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Filtered sales data based on region filter</span>
</span></span><span class="line"><span class="cl">  <span class="n">filtered_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">region_filter</span> <span class="o">!=</span> <span class="s">&#34;All&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Region</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">region_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">sales_table</span> <span class="o">&lt;-</span> <span class="nf">renderTable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="o">$</span><span class="n">Sales</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;$&#34;</span><span class="p">,</span> <span class="nf">format</span><span class="p">(</span><span class="n">data</span><span class="o">$</span><span class="n">Sales</span><span class="p">,</span> <span class="n">big.mark</span> <span class="o">=</span> <span class="s">&#34;,&#34;</span><span class="p">,</span> <span class="n">trim</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
<div id="tabset-4-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;region_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;floppy-disk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">),</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-9">
  <div class="code-with-filename-label" id="code-filename-9"><span class="font-mono text-sm">app_2.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">Session</span><span class="p">,</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">h2</span><span class="p">(</span><span class="s2">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;region_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;floppy-disk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">output_table</span><span class="p">(</span><span class="s2">&#34;sales_table&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Toolbar in header, footer, label&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;More placeholder text.&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_footer</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Footer content here.&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for outputs&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">:</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">output</span><span class="p">:</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">save_btn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Saved&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">notification_show</span><span class="p">(</span><span class="s2">&#34;Saving&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Product&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Region&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">850</span><span class="p">,</span> <span class="mi">1450</span><span class="p">,</span> <span class="mi">920</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Filtered sales data based on region filter</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">filtered_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">input</span><span class="o">.</span><span class="n">region_filter</span><span class="p">()</span> <span class="o">!=</span> <span class="s2">&#34;All&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Region&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">region_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.table</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">hide</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="s2">&#34;index&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">format</span><span class="p">({</span><span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="s2">&#34;$</span><span class="si">{:,.0f}</span><span class="s2">&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">set_table_styles</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;th&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;border-bottom&#34;</span><span class="p">,</span> <span class="s2">&#34;1px solid black&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;8px 6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;text-align&#34;</span><span class="p">,</span> <span class="s2">&#34;left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;td&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">            <span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
</div>
<h3 id="toolbars-in-card-footers">Toolbars in Card Footers
</h3>
<p>We can also add toolbars in card footers. Footers are a great spot for secondary actions like sharing, exporting, or navigating to related content.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-5" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-5-1">R</a></li>
<li><a href="#tabset-5-2">Python</a></li>
</ul>
<div id="tabset-5-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;share_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Share&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;share-nodes&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;export_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Export&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;download&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-11">
  <div class="code-with-filename-label" id="code-filename-11"><span class="font-mono text-sm">app_3.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h2</span><span class="p">(</span><span class="s">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;region_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;floppy-disk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">tableOutput</span><span class="p">(</span><span class="s">&#34;sales_table&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Toolbar in header, footer, label&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;More placeholder text.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;share_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Share&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;share-nodes&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;export_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Export&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;download&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for outputs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">  <span class="n">sales_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">data.frame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">Product</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Region</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Sales</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1200</span><span class="p">,</span> <span class="m">850</span><span class="p">,</span> <span class="m">1450</span><span class="p">,</span> <span class="m">920</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">stringsAsFactors</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Filtered sales data based on region filter</span>
</span></span><span class="line"><span class="cl">  <span class="n">filtered_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">region_filter</span> <span class="o">!=</span> <span class="s">&#34;All&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Region</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">region_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">sales_table</span> <span class="o">&lt;-</span> <span class="nf">renderTable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="o">$</span><span class="n">Sales</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;$&#34;</span><span class="p">,</span> <span class="nf">format</span><span class="p">(</span><span class="n">data</span><span class="o">$</span><span class="n">Sales</span><span class="p">,</span> <span class="n">big.mark</span> <span class="o">=</span> <span class="s">&#34;,&#34;</span><span class="p">,</span> <span class="n">trim</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
<div id="tabset-5-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;share_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Share&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;share-nodes&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;export_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Export&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;download&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">),</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-13">
  <div class="code-with-filename-label" id="code-filename-13"><span class="font-mono text-sm">app_3.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">Session</span><span class="p">,</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">h2</span><span class="p">(</span><span class="s2">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;region_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;floppy-disk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">output_table</span><span class="p">(</span><span class="s2">&#34;sales_table&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Toolbar in header, footer, label&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;More placeholder text.&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;share_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Share&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;share-nodes&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;export_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Export&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;download&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for outputs&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">:</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">output</span><span class="p">:</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Product&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Region&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">850</span><span class="p">,</span> <span class="mi">1450</span><span class="p">,</span> <span class="mi">920</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Filtered sales data based on region filter</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">filtered_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">input</span><span class="o">.</span><span class="n">region_filter</span><span class="p">()</span> <span class="o">!=</span> <span class="s2">&#34;All&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Region&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">region_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.table</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">hide</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="s2">&#34;index&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">format</span><span class="p">({</span><span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="s2">&#34;$</span><span class="si">{:,.0f}</span><span class="s2">&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">set_table_styles</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;th&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;border-bottom&#34;</span><span class="p">,</span> <span class="s2">&#34;1px solid black&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;8px 6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;text-align&#34;</span><span class="p">,</span> <span class="s2">&#34;left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;td&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">            <span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
</div>
<p>Notice in these examples we use both toolbar buttons and toolbar select inputs. Both of these inputs also have update functions.</p>
<p>For example you might want to update an icon on submit or change out the list of choices depending on user input. We&rsquo;ll talk more about that in the next section.</p>
<h3 id="updating-toolbar-inputs">Updating Toolbar Inputs
</h3>
<p>Toolbar input buttons and selects can be updated dynamically, just like regular Shiny inputs. This is useful for doing things like changing icons after an action (ex. to show a checkmark after saving) or updating available choices based on app state.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-6" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-6-1">R</a></li>
<li><a href="#tabset-6-2">Python</a></li>
</ul>
<div id="tabset-6-1">
<h3 id="a-simple-example-of-updating-a-button">A simple example of updating a button
</h3>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">save_btn</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Change icon to checkmark and update label</span>
</span></span><span class="line"><span class="cl">  <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Saved&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span> <span class="o">=</span> <span class="n">session</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="nf">showNotification</span><span class="p">(</span><span class="s">&#34;Changes saved!&#34;</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span></span></span></code></pre></div></div>
<p>See the complete app code below for more complex examples, including <code>update_toolbar_input_select()</code></p>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-15">
  <div class="code-with-filename-label" id="code-filename-15"><span class="font-mono text-sm">app_4.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h2</span><span class="p">(</span><span class="s">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;right-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Switch to filtering by Product&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;floppy-disk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">tableOutput</span><span class="p">(</span><span class="s">&#34;sales_table&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Toolbar in header, footer, label&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;More placeholder text.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;share_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Share&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;share-nodes&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;export_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Export&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;download&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for outputs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Track current filter mode</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">filter_mode</span> <span class="o">&lt;-</span> <span class="nf">reactiveVal</span><span class="p">(</span><span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle save button click</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">save_btn</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Saved&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">showNotification</span><span class="p">(</span><span class="s">&#34;Saving&#34;</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle toggle filter button click</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">toggle_filter</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">filter_mode</span><span class="p">(</span><span class="s">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">selected</span> <span class="o">=</span> <span class="s">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Region&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Switch to filtering by Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">filter_mode</span><span class="p">(</span><span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">selected</span> <span class="o">=</span> <span class="s">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Product&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Switch to filtering by Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">  <span class="n">sales_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">data.frame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">Product</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Region</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Sales</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1200</span><span class="p">,</span> <span class="m">850</span><span class="p">,</span> <span class="m">1450</span><span class="p">,</span> <span class="m">920</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">stringsAsFactors</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Filtered sales data based on current filter</span>
</span></span><span class="line"><span class="cl">  <span class="n">filtered_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">data_filter</span> <span class="o">!=</span> <span class="s">&#34;All&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">if</span> <span class="p">(</span><span class="nf">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Region</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">data_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Product</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">data_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">sales_table</span> <span class="o">&lt;-</span> <span class="nf">renderTable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">nrow</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">data</span><span class="o">$</span><span class="n">Sales</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;$&#34;</span><span class="p">,</span> <span class="nf">format</span><span class="p">(</span><span class="n">data</span><span class="o">$</span><span class="n">Sales</span><span class="p">,</span> <span class="n">big.mark</span> <span class="o">=</span> <span class="s">&#34;,&#34;</span><span class="p">,</span> <span class="n">trim</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
<div id="tabset-6-2">
<h3 id="a-simple-example-of-updating-a-button-1">A simple example of updating a button
</h3>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">save_btn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Saved&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">notification_show</span><span class="p">(</span><span class="s2">&#34;Changes saved!&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>See the complete app code below for more complex examples, including <code>ui.update_toolbar_input_select()</code></p>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-17">
  <div class="code-with-filename-label" id="code-filename-17"><span class="font-mono text-sm">app_4.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">Session</span><span class="p">,</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">h2</span><span class="p">(</span><span class="s2">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;right-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Switch to filtering by Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;floppy-disk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">output_table</span><span class="p">(</span><span class="s2">&#34;sales_table&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Toolbar in header, footer, label&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;More placeholder text.&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_footer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;share_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Share&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;share-nodes&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;export_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Export&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;download&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for outputs&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">:</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">output</span><span class="p">:</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Track current filter mode</span>
</span></span><span class="line"><span class="cl">    <span class="n">filter_mode</span> <span class="o">=</span> <span class="n">reactive</span><span class="o">.</span><span class="n">value</span><span class="p">(</span><span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">save_btn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Saved&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">notification_show</span><span class="p">(</span><span class="s2">&#34;Saving&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle toggle filter button click</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">toggle_filter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Region&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">filter_mode</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">selected</span><span class="o">=</span><span class="s2">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Switch to filtering by Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">filter_mode</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">selected</span><span class="o">=</span><span class="s2">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Switch to filtering by Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Product&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Region&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">850</span><span class="p">,</span> <span class="mi">1450</span><span class="p">,</span> <span class="mi">920</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Filtered sales data based on current filter</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">filtered_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()</span> <span class="o">!=</span> <span class="s2">&#34;All&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Region&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Region&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Product&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.table</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">hide</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="s2">&#34;index&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">format</span><span class="p">({</span><span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="s2">&#34;$</span><span class="si">{:,.0f}</span><span class="s2">&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">set_table_styles</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;th&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;border-bottom&#34;</span><span class="p">,</span> <span class="s2">&#34;1px solid black&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;8px 6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;text-align&#34;</span><span class="p">,</span> <span class="s2">&#34;left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;td&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">            <span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
</div>
<h2 id="attaching-controls-to-inputs">Attaching Controls to Inputs
</h2>
<p>Toolbars aren&rsquo;t just for headers and footers. We can also put them in input labels to attach small controls directly to inputs.
This pattern works well for adding quick actions like preset values or reset buttons next to numeric inputs.</p>
<figure>
<img src="https://opensource.posit.co/blog/2026-05-26_introducing-toolbars/card2.png" alt="Quantity input with an inline toolbar in its label offering preset buttons (10, 50, 100) and a reset button." />
<figcaption aria-hidden="true">Quantity input with an inline toolbar in its label offering preset buttons (10, 50, 100) and a reset button.</figcaption>
</figure>
<p>Here&rsquo;s an example of a quantity input with toolbar buttons for setting preset values and resetting:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-7" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-7-1">R</a></li>
<li><a href="#tabset-7-2">Python</a></li>
</ul>
<div id="tabset-7-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">numericInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;quantity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">label</span> <span class="o">=</span> <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Quantity:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_spacer</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 10&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 50&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 100&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;rotate-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Reset to 1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">value</span> <span class="o">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">min</span> <span class="o">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">max</span> <span class="o">=</span> <span class="m">1000</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-19">
  <div class="code-with-filename-label" id="code-filename-19"><span class="font-mono text-sm">app_5.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h2</span><span class="p">(</span><span class="s">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;right-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Switch to filtering by Product&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;floppy-disk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">tableOutput</span><span class="p">(</span><span class="s">&#34;sales_table&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Toolbar in Input Label&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">numericInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;quantity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">label</span> <span class="o">=</span> <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s">&#34;Quantity:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_spacer</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 10&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 50&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 100&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;rotate-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Reset to 1&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="n">value</span> <span class="o">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">min</span> <span class="o">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">max</span> <span class="o">=</span> <span class="m">1000</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;quantity_output&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Placeholder for outputs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Track current filter mode</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">filter_mode</span> <span class="o">&lt;-</span> <span class="nf">reactiveVal</span><span class="p">(</span><span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle save button click</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">save_btn</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Saved&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">showNotification</span><span class="p">(</span><span class="s">&#34;Saving&#34;</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle toggle filter button click</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">toggle_filter</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">filter_mode</span><span class="p">(</span><span class="s">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">selected</span> <span class="o">=</span> <span class="s">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Region&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Switch to filtering by Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">filter_mode</span><span class="p">(</span><span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">selected</span> <span class="o">=</span> <span class="s">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Product&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Switch to filtering by Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle numeric input preset buttons</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_preset_10</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_preset_50</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_preset_100</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_reset</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">quantity_output</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">paste</span><span class="p">(</span><span class="s">&#34;Current quantity:&#34;</span><span class="p">,</span> <span class="n">input</span><span class="o">$</span><span class="n">quantity</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">  <span class="n">sales_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">data.frame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">Product</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Region</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Sales</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1200</span><span class="p">,</span> <span class="m">850</span><span class="p">,</span> <span class="m">1450</span><span class="p">,</span> <span class="m">920</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">stringsAsFactors</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Filtered sales data based on current filter</span>
</span></span><span class="line"><span class="cl">  <span class="n">filtered_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">data_filter</span> <span class="o">!=</span> <span class="s">&#34;All&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">if</span> <span class="p">(</span><span class="nf">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Region</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">data_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Product</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">data_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">sales_table</span> <span class="o">&lt;-</span> <span class="nf">renderTable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">nrow</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">data</span><span class="o">$</span><span class="n">Sales</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;$&#34;</span><span class="p">,</span> <span class="nf">format</span><span class="p">(</span><span class="n">data</span><span class="o">$</span><span class="n">Sales</span><span class="p">,</span> <span class="n">big.mark</span> <span class="o">=</span> <span class="s">&#34;,&#34;</span><span class="p">,</span> <span class="n">trim</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
<div id="tabset-7-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_numeric</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;quantity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">label</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Quantity:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_spacer</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;btn_preset_10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;btn_preset_50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;btn_preset_100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;btn_reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;rotate-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Reset to 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">value</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nb">min</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nb">max</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-21">
  <div class="code-with-filename-label" id="code-filename-21"><span class="font-mono text-sm">app_5.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">Session</span><span class="p">,</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">h2</span><span class="p">(</span><span class="s2">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;right-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Switch to filtering by Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;floppy-disk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">output_table</span><span class="p">(</span><span class="s2">&#34;sales_table&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Toolbar in Input Label&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">input_numeric</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;quantity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">label</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;Quantity:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_spacer</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_preset_10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_preset_50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_preset_100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;rotate-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Reset to 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">value</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">min</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">max</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;quantity_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for an input&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Placeholder for outputs&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">:</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">output</span><span class="p">:</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Track current filter mode</span>
</span></span><span class="line"><span class="cl">    <span class="n">filter_mode</span> <span class="o">=</span> <span class="n">reactive</span><span class="o">.</span><span class="n">value</span><span class="p">(</span><span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">save_btn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Saved&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">notification_show</span><span class="p">(</span><span class="s2">&#34;Saving&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle toggle filter button click</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">toggle_filter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Region&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">filter_mode</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">selected</span><span class="o">=</span><span class="s2">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Switch to filtering by Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">filter_mode</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">selected</span><span class="o">=</span><span class="s2">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Switch to filtering by Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle numeric input preset buttons</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_preset_10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_preset_50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_preset_100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_reset</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">quantity_output</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Current quantity: </span><span class="si">{</span><span class="nb">input</span><span class="o">.</span><span class="n">quantity</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Product&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Region&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">850</span><span class="p">,</span> <span class="mi">1450</span><span class="p">,</span> <span class="mi">920</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Filtered sales data based on current filter</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">filtered_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()</span> <span class="o">!=</span> <span class="s2">&#34;All&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Region&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Region&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Product&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.table</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">hide</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="s2">&#34;index&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">format</span><span class="p">({</span><span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="s2">&#34;$</span><span class="si">{:,.0f}</span><span class="s2">&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">set_table_styles</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;th&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;border-bottom&#34;</span><span class="p">,</span> <span class="s2">&#34;1px solid black&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;8px 6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;text-align&#34;</span><span class="p">,</span> <span class="s2">&#34;left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;td&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">            <span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
</div>
<p>The key here is passing the toolbar directly as the label. Include your label text as the first element, then use <code>toolbar_spacer()</code> to push the buttons to the right side. Toolbars automatically expand to full width when used as input labels, creating a compact control.</p>
<h2 id="complex-text-inputs-with-toolbars">Complex Text Inputs with Toolbars
</h2>
<p>Chatbots and AI applications have accelerated the need for more toolbars within text input areas to provide tools for attaching context or manipulating text.
Some newer Shiny components like <code>input_submit_textarea()</code> allow you to pass a toolbar directly into the input itself.</p>
<p>Here&rsquo;s a message composer example that includes buttons for attaching files, selecting a priority, and clearing the message:</p>
<figure>
<img src="https://opensource.posit.co/blog/2026-05-26_introducing-toolbars/card3.png" alt="Message composer textarea with a submit-area toolbar (attach, priority flag, delete) and a Submit button, beside a message history panel." />
<figcaption aria-hidden="true">Message composer textarea with a submit-area toolbar (attach, priority flag, delete) and a Submit button, beside a message history panel.</figcaption>
</figure>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-8" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-8-1">R</a></li>
<li><a href="#tabset-8-2">Python</a></li>
</ul>
<div id="tabset-8-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">input_submit_textarea</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Your message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;Type your message here...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">toolbar</span> <span class="o">=</span> <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;left&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;attach_file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Attach&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;paperclip&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Low&#34;</span><span class="p">,</span> <span class="s">&#34;Medium&#34;</span><span class="p">,</span> <span class="s">&#34;High&#34;</span><span class="p">,</span> <span class="s">&#34;Urgent&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;flag&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;clear_message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Clear&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;trash&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-23">
  <div class="code-with-filename-label" id="code-filename-23"><span class="font-mono text-sm">app_6.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h2</span><span class="p">(</span><span class="s">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;right-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Switch to filtering by Product&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;floppy-disk&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">tableOutput</span><span class="p">(</span><span class="s">&#34;sales_table&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Toolbar in Input Label&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">numericInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;quantity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">label</span> <span class="o">=</span> <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s">&#34;Quantity:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_spacer</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 10&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 50&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_preset_100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">show_label</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Set to 100&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;btn_reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;rotate-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="n">tooltip</span> <span class="o">=</span> <span class="s">&#34;Reset to 1&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;right&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="n">value</span> <span class="o">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">min</span> <span class="o">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">max</span> <span class="o">=</span> <span class="m">1000</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;quantity_output&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">input_submit_textarea</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Your message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;Type your message here...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">toolbar</span> <span class="o">=</span> <span class="nf">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">align</span> <span class="o">=</span> <span class="s">&#34;left&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;attach_file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Attach&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;paperclip&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Low&#34;</span><span class="p">,</span> <span class="s">&#34;Medium&#34;</span><span class="p">,</span> <span class="s">&#34;High&#34;</span><span class="p">,</span> <span class="s">&#34;Urgent&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;flag&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;clear_message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Clear&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;trash&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">h4</span><span class="p">(</span><span class="s">&#34;Message History&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">uiOutput</span><span class="p">(</span><span class="s">&#34;message_history&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="n">style</span> <span class="o">=</span> <span class="s">&#34;max-height: 400px; overflow-y: auto;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Track current filter mode</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">filter_mode</span> <span class="o">&lt;-</span> <span class="nf">reactiveVal</span><span class="p">(</span><span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle save button click</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">save_btn</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">icon</span> <span class="o">=</span> <span class="nf">icon</span><span class="p">(</span><span class="s">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Saved&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">showNotification</span><span class="p">(</span><span class="s">&#34;Saving&#34;</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle toggle filter button click</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">toggle_filter</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">filter_mode</span><span class="p">(</span><span class="s">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">selected</span> <span class="o">=</span> <span class="s">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Region&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Switch to filtering by Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">filter_mode</span><span class="p">(</span><span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">choices</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;All&#34;</span><span class="p">,</span> <span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">selected</span> <span class="o">=</span> <span class="s">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Switch to Product&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Switch to filtering by Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">update_tooltip</span><span class="p">(</span><span class="s">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle numeric input preset buttons</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_preset_10</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_preset_50</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_preset_100</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn_reset</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateNumericInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">quantity_output</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">paste</span><span class="p">(</span><span class="s">&#34;Current quantity:&#34;</span><span class="p">,</span> <span class="n">input</span><span class="o">$</span><span class="n">quantity</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">  <span class="n">sales_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">data.frame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">Product</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s">&#34;Widget D&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Region</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;North&#34;</span><span class="p">,</span> <span class="s">&#34;South&#34;</span><span class="p">,</span> <span class="s">&#34;East&#34;</span><span class="p">,</span> <span class="s">&#34;West&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">Sales</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1200</span><span class="p">,</span> <span class="m">850</span><span class="p">,</span> <span class="m">1450</span><span class="p">,</span> <span class="m">920</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">stringsAsFactors</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Filtered sales data based on current filter</span>
</span></span><span class="line"><span class="cl">  <span class="n">filtered_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">data_filter</span> <span class="o">!=</span> <span class="s">&#34;All&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">if</span> <span class="p">(</span><span class="nf">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;Region&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Region</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">data_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">data[data</span><span class="o">$</span><span class="n">Product</span> <span class="o">==</span> <span class="n">input</span><span class="o">$</span><span class="n">data_filter</span><span class="p">,</span> <span class="n">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">sales_table</span> <span class="o">&lt;-</span> <span class="nf">renderTable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="nf">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">nrow</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">data</span><span class="o">$</span><span class="n">Sales</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;$&#34;</span><span class="p">,</span> <span class="nf">format</span><span class="p">(</span><span class="n">data</span><span class="o">$</span><span class="n">Sales</span><span class="p">,</span> <span class="n">big.mark</span> <span class="o">=</span> <span class="s">&#34;,&#34;</span><span class="p">,</span> <span class="n">trim</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Handle message input with toolbar</span>
</span></span><span class="line"><span class="cl">  <span class="n">messages</span> <span class="o">&lt;-</span> <span class="nf">reactiveVal</span><span class="p">(</span><span class="nf">character</span><span class="p">(</span><span class="m">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">message</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_message</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;[&#34;</span><span class="p">,</span> <span class="n">input</span><span class="o">$</span><span class="n">priority</span><span class="p">,</span> <span class="s">&#34;] &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">input</span><span class="o">$</span><span class="n">message</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">messages</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="nf">messages</span><span class="p">(),</span> <span class="n">new_message</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">attach_file</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">showNotification</span><span class="p">(</span><span class="s">&#34;File attachment feature clicked&#34;</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">clear_message</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">updateTextAreaInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s">&#34;message&#34;</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="s">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">message_history</span> <span class="o">&lt;-</span> <span class="nf">renderUI</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="nf">messages</span><span class="p">())</span> <span class="o">==</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">p</span><span class="p">(</span><span class="s">&#34;No messages yet&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">tagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">lapply</span><span class="p">(</span><span class="nf">messages</span><span class="p">(),</span> <span class="kr">function</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">msg</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;mb-2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">style</span> <span class="o">=</span> <span class="s">&#34;background-color: #e9ecef; padding: 10px 15px; border-radius: 8px;&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
<div id="tabset-8-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_submit_textarea</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Your message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;Type your message here...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">toolbar</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;attach_file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Attach&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;paperclip&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;Low&#34;</span><span class="p">,</span> <span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="s2">&#34;High&#34;</span><span class="p">,</span> <span class="s2">&#34;Urgent&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;flag&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;clear_message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Clear&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;trash&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">align</span><span class="o">=</span><span class="s2">&#34;left&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<details>
<summary>
Complete app code
</summary>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-25">
  <div class="code-with-filename-label" id="code-filename-25"><span class="font-mono text-sm">app_6.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">Session</span><span class="p">,</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">h2</span><span class="p">(</span><span class="s2">&#34;Example App&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales Data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;right-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Switch to filtering by Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;floppy-disk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">output_table</span><span class="p">(</span><span class="s2">&#34;sales_table&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Toolbar in Input Label&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">input_numeric</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;quantity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">label</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;Quantity:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_spacer</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_preset_10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_preset_50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 50&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_preset_100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">show_label</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Set to 100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;btn_reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Reset&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;rotate-left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">tooltip</span><span class="o">=</span><span class="s2">&#34;Reset to 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">align</span><span class="o">=</span><span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">value</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">min</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">max</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;quantity_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Toolbar in Text Input Submit Area: Message Composer&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_body</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">input_submit_textarea</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Your message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;Type your message here...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">toolbar</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">toolbar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;attach_file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Attach&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;paperclip&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Priority&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;Low&#34;</span><span class="p">,</span> <span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="s2">&#34;High&#34;</span><span class="p">,</span> <span class="s2">&#34;Urgent&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;flag&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_divider</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;clear_message&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Clear&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;trash&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">align</span><span class="o">=</span><span class="s2">&#34;left&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">h4</span><span class="p">(</span><span class="s2">&#34;Message History&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">output_ui</span><span class="p">(</span><span class="s2">&#34;message_history&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">style</span><span class="o">=</span><span class="s2">&#34;max-height: 400px; overflow-y: auto;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">col_widths</span><span class="o">=</span><span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">:</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">output</span><span class="p">:</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Track current filter mode</span>
</span></span><span class="line"><span class="cl">    <span class="n">filter_mode</span> <span class="o">=</span> <span class="n">reactive</span><span class="o">.</span><span class="n">value</span><span class="p">(</span><span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">save_btn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;save_btn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;check&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Saved&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">notification_show</span><span class="p">(</span><span class="s2">&#34;Saving&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle toggle filter button click</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">toggle_filter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Region&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">filter_mode</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">selected</span><span class="o">=</span><span class="s2">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Switch to filtering by Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">filter_mode</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;data_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;All&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">selected</span><span class="o">=</span><span class="s2">&#34;All&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_toolbar_input_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;toggle_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Switch to Product&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;toggle_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Switch to filtering by Product&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">update_tooltip</span><span class="p">(</span><span class="s2">&#34;data_filter_tooltip&#34;</span><span class="p">,</span> <span class="s2">&#34;Region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle numeric input preset buttons</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_preset_10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_preset_50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_preset_100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">btn_reset</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_numeric</span><span class="p">(</span><span class="s2">&#34;quantity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">quantity_output</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Current quantity: </span><span class="si">{</span><span class="nb">input</span><span class="o">.</span><span class="n">quantity</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Sample sales data</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Product&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Widget A&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span> <span class="s2">&#34;Widget D&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Region&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">850</span><span class="p">,</span> <span class="mi">1450</span><span class="p">,</span> <span class="mi">920</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Filtered sales data based on current filter</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">filtered_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">sales_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()</span> <span class="o">!=</span> <span class="s2">&#34;All&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">filter_mode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Region&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Region&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;Product&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="nb">input</span><span class="o">.</span><span class="n">data_filter</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Render the table</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.table</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sales_table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">filtered_data</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">hide</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="s2">&#34;index&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">format</span><span class="p">({</span><span class="s2">&#34;Sales&#34;</span><span class="p">:</span> <span class="s2">&#34;$</span><span class="si">{:,.0f}</span><span class="s2">&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">set_table_styles</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;th&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;border-bottom&#34;</span><span class="p">,</span> <span class="s2">&#34;1px solid black&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;8px 6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;text-align&#34;</span><span class="p">,</span> <span class="s2">&#34;left&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="s2">&#34;selector&#34;</span><span class="p">:</span> <span class="s2">&#34;td&#34;</span><span class="p">,</span> <span class="s2">&#34;props&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="s2">&#34;padding&#34;</span><span class="p">,</span> <span class="s2">&#34;6px&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="p">]},</span>
</span></span><span class="line"><span class="cl">            <span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle message input with toolbar</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span> <span class="o">=</span> <span class="n">reactive</span><span class="o">.</span><span class="n">value</span><span class="p">([])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">new_message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="nb">input</span><span class="o">.</span><span class="n">priority</span><span class="p">()</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="nb">input</span><span class="o">.</span><span class="n">message</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">messages</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">messages</span><span class="o">.</span><span class="n">get</span><span class="p">()</span> <span class="o">+</span> <span class="p">[</span><span class="n">new_message</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">attach_file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">notification_show</span><span class="p">(</span><span class="s2">&#34;File attachment feature clicked&#34;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="s2">&#34;message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">clear_message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">update_text_area</span><span class="p">(</span><span class="s2">&#34;message&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.ui</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">message_history</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">messages</span><span class="o">.</span><span class="n">get</span><span class="p">())</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;No messages yet&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">TagList</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">msg</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;mb-2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">style</span><span class="o">=</span><span class="s2">&#34;background-color: #e9ecef; padding: 10px 15px; border-radius: 8px;&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">for</span> <span class="n">msg</span> <span class="ow">in</span> <span class="n">messages</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</details>
</div>
</div>
<p>The <code>input_submit_textarea()</code> component accepts a <code>toolbar</code> parameter directly, making it easy to add contextual controls right where users need them.</p>
<h2 id="toolbar-input-components">Toolbar Input Components
</h2>
<p>Here&rsquo;s a quick reference of the toolbar input components available in both <a href="https://rstudio.github.io/bslib/" target="_blank" rel="noopener">bslib</a> and <a href="https://shiny.posit.co/py/" target="_blank" rel="noopener">py-shiny</a>:</p>
<table>
  <thead>
      <tr>
          <th>R (bslib)</th>
          <th>Python (py-shiny)</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://rstudio.github.io/bslib/reference/toolbar.html" target="_blank" rel="noopener"><code>toolbar()</code></a></td>
          <td><a href="https://shiny.posit.co/py/api/ui.toolbar.html" target="_blank" rel="noopener"><code>ui.toolbar()</code></a></td>
          <td>The container for toolbar inputs</td>
      </tr>
      <tr>
          <td><a href="https://rstudio.github.io/bslib/reference/toolbar_input_button.html" target="_blank" rel="noopener"><code>toolbar_input_button()</code></a></td>
          <td><a href="https://shiny.posit.co/py/api/ui.toolbar_input_button.html" target="_blank" rel="noopener"><code>ui.toolbar_input_button()</code></a></td>
          <td>A small action button</td>
      </tr>
      <tr>
          <td><a href="https://rstudio.github.io/bslib/reference/toolbar_input_select.html" target="_blank" rel="noopener"><code>toolbar_input_select()</code></a></td>
          <td><a href="https://shiny.posit.co/py/api/ui.toolbar_input_select.html" target="_blank" rel="noopener"><code>ui.toolbar_input_select()</code></a></td>
          <td>A compact dropdown select</td>
      </tr>
      <tr>
          <td><a href="https://rstudio.github.io/bslib/reference/toolbar_divider.html" target="_blank" rel="noopener"><code>toolbar_divider()</code></a></td>
          <td><a href="https://shiny.posit.co/py/api/ui.toolbar_divider.html" target="_blank" rel="noopener"><code>ui.toolbar_divider()</code></a></td>
          <td>A visual separator between groups of controls</td>
      </tr>
      <tr>
          <td><a href="https://rstudio.github.io/bslib/reference/toolbar_divider.html" target="_blank" rel="noopener"><code>toolbar_spacer()</code></a></td>
          <td><a href="https://shiny.posit.co/py/api/ui.toolbar_spacer.html" target="_blank" rel="noopener"><code>ui.toolbar_spacer()</code></a></td>
          <td>A flexible spacer that pushes subsequent items to the opposite end of the toolbar</td>
      </tr>
  </tbody>
</table>
<p>Each toolbar input also has a corresponding <code>update_*</code> function (R) / <code>update_*</code> method on <code>ui</code> (Python) for modifying it dynamically.</p>
<h2 id="summary">Summary
</h2>
<p>Toolbars provide a flexible way to add compact controls to your Shiny apps:</p>
<ul>
<li><strong>Card headers and footers</strong> - Associate controls directly with card content</li>
<li><strong>Input labels</strong> - Add quick actions like reset or format buttons</li>
<li><strong>Text area inputs</strong> - Build rich message composers with attachment and formatting tools</li>
</ul>
<p>Their small footprint makes them perfect for dashboards and modern app interfaces where screen real estate is at a premium. Give them a try in your next Shiny app!</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">What else is new?</span>
</div>
<div class="callout-body">
<p>See what else is new in the <a href="https://rstudio.github.io/bslib/news/index.html" target="_blank" rel="noopener">bslib changelog</a> (R) and the <a href="https://github.com/posit-dev/py-shiny/blob/main/CHANGELOG.md" target="_blank" rel="noopener">py-shiny changelog</a> (Python).</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-26_introducing-toolbars/preview.png" length="193556" type="image/png" />
    </item>
    <item>
      <title>A memory model for production async R: mirai 2.7.0 and mori 0.2.0</title>
      <link>https://opensource.posit.co/blog/2026-05-12_production-async-r/</link>
      <pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-12_production-async-r/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<p>Async R has always come with tradeoffs. Parallel batch compute meant duplicated data and an unbounded queue that could push your session out of memory, while Shiny gave you an event loop but background-worker submissions had no way to tell you when to slow down.</p>
<p>Shared memory across processes is standard in other programming languages. In R it&rsquo;s been either platform-specific (<code>fork</code> on Unix-alikes, unsafe from GUIs like Positron or with multi-threaded code) or file-format-specific (memory-mapped Parquet).</p>
<p>Two CRAN releases address those constraints directly:</p>
<ul>
<li><strong><a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai 2.7.0</a></strong> — <code>daemons(memory = ...)</code> puts a hard byte-level cap on the dispatcher queue. <code>try_mirai()</code> returns <code>NULL</code> immediately when that cap is hit, instead of blocking your session. The dispatcher itself runs as an in-process thread now; and through <a href="https://nanonext.r-lib.org/" target="_blank" rel="noopener">nanonext 1.9.0</a> underneath, peak memory during serialized sends has halved.</li>
<li><strong><a href="https://shikokuchuo.net/mori/" target="_blank" rel="noopener">mori 0.2.0</a></strong> — graduates from experimental status, with a stable wire format underneath. Sub-lists and extracted elements now expose stable path-form names (<code>/mori_abc[2,3]</code>) that any process can attach to via <code>map_shared()</code> directly, without going through the parent.</li>
</ul>
<h2 id="building-out-the-foundations">Building out the foundations
</h2>
<p>mirai already shipped a lot of what production async needs: microsecond round-trip latency, FIFO scheduling, task cancellation, OpenTelemetry tracing, reproducible parallel RNG, custom serialization for torch / Arrow / polars, an event-driven promises method for Shiny ExtendedTask, and deployment across SSH / Slurm / Posit Workbench.</p>
<p>What these releases add is more foundational. R hasn&rsquo;t really had a built-in memory model for asynchronous work — no way to size a task queue in bytes, and no portable way to share data across processes without paying for a full copy. mirai 2.7.0 introduces the first: a bounded queue built into the dispatcher, with <code>try_mirai()</code> exposing non-blocking submission under this constraint. mori 0.2.0 brings the second to a stable footing: a self-describing shared-memory format, addressable down to individual list elements by name, that any process on the machine can attach to.</p>
<p>Together they extend what R can credibly be used for — streaming pipelines, multi-stage data flows, and event-loop applications that previously had to reach outside the language.</p>
<h2 id="mirai-270--bounded-queues-and-try_mirai">mirai 2.7.0 — bounded queues and <code>try_mirai()</code>
</h2>
<h3 id="daemonsmemory--"><code>daemons(memory = ...)</code>
</h3>
<p>The dispatcher is the single point in your session where every <code>mirai()</code> call lands before being routed to a daemon. Each call carries its serialized arguments — the queued payload — and that payload sits in memory until a daemon is ready for it. By default the queue is unbounded, which is fine until it isn&rsquo;t.</p>
<p><code>daemons()</code> now takes a <code>memory</code> argument that caps the approximate total payload of queued tasks at the dispatcher, in MB:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mirai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">4</span><span class="p">,</span> <span class="n">memory</span> <span class="o">=</span> <span class="m">100</span><span class="p">)</span>  <span class="c1"># 100 MB queue capacity</span></span></span></code></pre></div></div>
<p>When the queued bytes are below the cap, <code>mirai()</code> calls submit normally. When the cap is reached, <code>mirai()</code> blocks the calling R thread until daemons drain the queue enough for the new payload to fit. Existing code keeps working — the only visible difference is that your session can no longer be pushed out of memory by a runaway producer.</p>
<p><code>status()</code> surfaces current and peak usage:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">status</span><span class="p">()</span><span class="o">$</span><span class="n">memory</span>
</span></span><span class="line"><span class="cl"><span class="c1">#     used     peak capacity </span>
</span></span><span class="line"><span class="cl"><span class="c1">#     12.4     87.3    100.0 </span></span></span></code></pre></div></div>
<p><code>peak</code> is the high-watermark queued payload over the lifetime of the dispatcher, which gives you an empirical way to size the cap. The recommended workflow is to run a representative workload with <code>memory = NULL</code> first (where <code>capacity</code> reports <code>NA</code>), observe <code>peak</code>, then set <code>memory</code> at or above that watermark.</p>
<p>If profiling isn&rsquo;t practical, the local machine has to fit your session, the dispatcher queue, and <code>n</code> daemon processes (each a full R session holding an in-flight payload while executing). Half of currently available RAM (<code>ps::ps_system_memory()[[&quot;avail&quot;]] / 2e6</code>, in MB) is a reasonable starting <code>memory</code> cap, revised down for large <code>n</code> or payloads. Remote daemons don&rsquo;t draw on the local budget, so when most workers are remote, the cap can be more generous.</p>
<p>For <code>n</code> itself, one less than your number of CPU cores is the right starting point for CPU-bound work (one core left for your session), while I/O-bound work (API calls, database queries, file reads) can comfortably exceed core count since idle daemons cost little.</p>
<p>The capacity is measured in <em>bytes</em>, not <em>task count</em>. A queue capped at 100 tasks can hold either 100 small tasks or 100 huge ones, and only the second runs out of memory. A queue capped at 100 MB never runs your session out of memory regardless of the mix.</p>
<h3 id="try_mirai"><code>try_mirai()</code>
</h3>
<p>A blocking submit is fine in a batch script, but not an event loop: a Shiny session that calls <code>mirai()</code> from inside an ExtendedTask can&rsquo;t afford to block while the queue drains, because that same session is also driving the UI for all users.</p>
<p><code>try_mirai()</code> returns immediately when the queue is full, instead of blocking. The semantics are simple:</p>
<ul>
<li>If the queue is below capacity, behaves identically to <code>mirai()</code> and returns a mirai.</li>
<li>If the queue is at capacity, returns <code>NULL</code> immediately without blocking, and leaves the caller to decide what to do next.</li>
</ul>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">m</span> <span class="o">&lt;-</span> <span class="nf">try_mirai</span><span class="p">({</span> <span class="nf">slow_thing</span><span class="p">()</span> <span class="p">})</span> <span class="o">%||%</span> <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;queue full&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>The three response strategies are: drop the task (best when the work is idempotent and frequent), retry later (queue the request behind a <code>later::later()</code> call), or propagate backpressure upstream — as in the example above, by raising a condition. Which is right is application-specific — <code>try_mirai()</code> hands you the flexibility.</p>
<p>If <code>memory</code> isn&rsquo;t set, or the daemon configuration has no dispatcher, <code>try_mirai()</code> always returns a mirai and is identical to <code>mirai()</code> — so the function is safe to use unconditionally; it only does anything different in the bounded-queue case.</p>
<p>The two halves — <code>daemons(memory = ...)</code> and <code>try_mirai()</code> — are the right combination for any event-loop integration. Set the cap to reflect what your session can actually afford to hold, submit through <code>try_mirai()</code>, and the application gracefully adapts to load instead of locking up or crashing.</p>
<h3 id="under-the-hood">Under the hood
</h3>
<p>A few transport-layer changes ship alongside 2.7.0 — invisible most of the time, important when payloads are large or throughput is high:</p>
<ul>
<li><strong>Dispatcher as a thread.</strong> The dispatcher is no longer a separate R process; it runs as an in-process thread, courtesy of a C-level reimplementation in nanonext. mirai round-trip latency is now in the tens of microseconds.</li>
<li><strong>Halved peak memory on sends.</strong> <a href="https://nanonext.r-lib.org/" target="_blank" rel="noopener">nanonext 1.9.0</a>, the transport underneath mirai, now writes serialized sends directly instead of going through an intermediate buffer. A 500 MB argument going to a daemon used to need ~1 GB of momentary headroom in your session; now it needs ~500 MB. The dispatcher&rsquo;s actual resident memory tracks the <code>daemons(memory = ...)</code> cap more accurately as a result.</li>
<li><strong>&gt;2 GB payloads on macOS and Windows.</strong> Transfers above 2 GB previously failed silently near the <code>INT_MAX</code>-byte boundary on those platforms. They now go through cleanly. Linux was already fine.</li>
</ul>
<h2 id="mori-020--out-of-experimental-status">mori 0.2.0 — out of experimental status
</h2>
<p>mori is a younger package — its <a href="https://opensource.posit.co/blog/2026-04-23_mori-0-1-0/">first CRAN release</a> was just last month — and its job is the other half of memory pressure: the data itself, rather than the queue. R processes don&rsquo;t share memory, and data crosses between them through message-passing. When eight workers each need the same 200 MB data frame, that&rsquo;s 1.6 GB of serialize / transfer / deserialize, plus eight separate copies of the data resident in RAM.</p>
<p><code>mori::share()</code> writes the object once into OS-level shared memory and returns an ALTREP (alternative representation) wrapper. Multiple processes on the same machine map the same physical pages — zero-copy, lazy, and managed by R&rsquo;s garbage collector. Pass the wrapper to a daemon and only the shared memory identifier (hundreds of bytes) crosses the wire; the daemon attaches and reads the pages directly.</p>
<p>We implemented this in mori 0.1.0, but 0.2.0 takes mori out of experimental status, landing a stable wire format. A key consequence of this is that every sub-list and extracted element of a shared region now has a stable identifier of its own. <code>shared_name()</code> emits path-form names (<code>/mori_abc_1[2]</code> for the second element of region <code>/mori_abc_1</code>, <code>/mori_abc_1[2,3]</code> for nested addressing), and <code>map_shared()</code> accepts the path form directly.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mori</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mirai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">lst</span> <span class="o">&lt;-</span> <span class="nf">share</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">list</span><span class="p">(</span><span class="n">weights</span> <span class="o">=</span> <span class="nf">rnorm</span><span class="p">(</span><span class="m">1e6</span><span class="p">),</span> <span class="n">targets</span> <span class="o">=</span> <span class="nf">rnorm</span><span class="p">(</span><span class="m">1e6</span><span class="p">),</span> <span class="n">inputs</span> <span class="o">=</span> <span class="nf">rnorm</span><span class="p">(</span><span class="m">1e6</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Sub-elements have their own path-form names</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shared_name</span><span class="p">(</span><span class="n">lst</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;/mori_abc_1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nf">shared_name</span><span class="p">(</span><span class="n">lst[[1]]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;/mori_abc_1[1]&#34;</span></span></span></code></pre></div></div>
<p>Pass the shared object directly to a daemon — the ALTREP wrapper serializes as just the name, and the daemon attaches on deserialize:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">mirai</span><span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="n">weights</span><span class="p">),</span> <span class="n">weights</span> <span class="o">=</span> <span class="n">lst[[1]]</span><span class="p">)</span><span class="n">[]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 1000000</span></span></span></code></pre></div></div>
<p>A consumer in a separate session that only has the name as a string — from a config file, message queue, or HTTP response — attaches with <code>map_shared()</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># In a separate R session</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">weights</span> <span class="o">&lt;-</span> <span class="n">mori</span><span class="o">::</span><span class="nf">map_shared</span><span class="p">(</span><span class="s">&#34;/mori_abc_1[1]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">length</span><span class="p">(</span><span class="n">weights</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 1000000</span></span></span></code></pre></div></div>
<p>A path-form name is a small thing on the wire – just a string – but it enables a lot:</p>
<ul>
<li><strong>Attach by name.</strong> Path-form identifiers are plain strings, so they fit anywhere a string can go: a queue payload, a config entry, an HTTP response, a database row. A consumer reads the name, calls <code>map_shared()</code>, and gets exactly that slice — the parent region never has to enter their session.</li>
<li><strong>Decoupled pipelines.</strong> Stage A shares a 100-column frame and tells stage B the names of the three columns to process. Stage B passes the name of its output to stage C, and so on. No stage holds more in memory than it actually uses, and no stage needs to know how its inputs were produced — only the names.</li>
<li><strong>Code symmetry.</strong> <code>map_shared(shared_name(x))</code> returns an equivalent of <code>x</code> whether <code>x</code> is a root region or a single leaf element, so consumer code doesn&rsquo;t need a special branch for sub-objects.</li>
<li><strong>Lifetime through one chain.</strong> Holding a reference to a leaf keeps the whole parent region alive automatically — the garbage collector walks the chain of dependencies — so a consumer never has to manually pin the root to stay safe.</li>
</ul>
<h3 id="composing-with-daemonsmemory--">Composing with <code>daemons(memory = ...)</code>
</h3>
<img src="https://opensource.posit.co/blog/2026-05-12_production-async-r/composition-diagram.svg" data-fig-alt="Diagram: the single call try_mirai({ f(y) }, y = share(x)) forks into two effects — submission to a bounded dispatcher queue (memory = 100 MB) and writing the data once into a shared-memory region (/mori_abc_1). Both feed a single daemon: the queue carries only the name, and the daemon attaches to shared memory by name via mmap." />
<p>The two memory tools compose. <code>mori::share()</code> shrinks each task&rsquo;s <em>queued payload</em> from megabytes to hundreds of bytes, which means the dispatcher&rsquo;s <code>memory</code> cap rarely fills up at all when workers are reading shared data. The pieces are complementary:</p>
<ul>
<li><code>mori::share()</code> removes the per-worker copy of large data.</li>
<li><code>daemons(memory = ...)</code> caps the dispatcher backlog when something <em>is</em> being sent.</li>
<li><code>try_mirai()</code> makes hitting that cap a non-event in an event loop.</li>
</ul>
<h2 id="putting-r-on-the-concurrency-map">Putting R on the concurrency map
</h2>
<p>The design of <code>daemons(memory = ...)</code>, <code>try_mirai()</code>, and <code>mori::share()</code> draws on how async runtimes in other languages handle the same problems. Here&rsquo;s the comparison:</p>
<p><strong>Byte-budgeted backpressure.</strong> Async frameworks elsewhere — Python&rsquo;s <code>asyncio.Queue</code>, Ray&rsquo;s <code>max_pending_tasks</code>, Tokio&rsquo;s bounded <code>mpsc</code> channels — measure queue capacity by task count. <code>daemons(memory = ...)</code> caps by <em>bytes</em> instead, which tracks what actually drives memory pressure.</p>
<p><strong>Non-blocking submission on a full queue.</strong> Tokio&rsquo;s <code>try_send</code> is the closest direct analogue: it returns immediately when the channel is full instead of blocking. <code>try_mirai()</code> provides the same semantics for R submission, with the additional advantage that the cap itself is byte-aware.</p>
<p><strong>Integrated cross-process zero-copy.</strong> Ray&rsquo;s Plasma object store offers cross-process zero-copy, but it&rsquo;s a heavyweight component — you run a Ray cluster to use it, not a function. <code>mori::share()</code> is just a function call, producing an ALTREP wrapper that consumer processes attach to via lazy reads, down to individual columns by name.</p>
<p>Combined with mirai&rsquo;s microsecond round-trip latency and event-driven C-level transport, these three pieces give R a concurrency story that stands on its own: byte-aware backpressure, non-blocking submission, and integrated zero-copy sharing.</p>
<h2 id="try-it">Try it
</h2>
<p>Install from CRAN:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;mirai&#34;</span><span class="p">,</span> <span class="s">&#34;mori&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
<p>Pointers from here:</p>
<ul>
<li><a href="https://mirai.r-lib.org/articles/v01-reference.html" target="_blank" rel="noopener">mirai reference vignette</a> — the memory-management section walks through <code>daemons(memory = ...)</code>, <code>status()$memory</code>, <code>try_mirai()</code>, and shared-memory composition.</li>
<li><a href="https://mirai.r-lib.org/articles/v02-promises.html" target="_blank" rel="noopener">mirai promises vignette</a> — Shiny ExtendedTask and event-driven async.</li>
<li><a href="https://shikokuchuo.net/mori/" target="_blank" rel="noopener">mori package site</a> — <code>share()</code>, <code>shared_name()</code> and <code>map_shared()</code>.</li>
<li><a href="https://nanonext.r-lib.org/" target="_blank" rel="noopener">nanonext package site</a> — the transport layer and concurrency primitives.</li>
<li>The <code>r-lib</code> agent skill in the <a href="https://github.com/posit-dev/skills" target="_blank" rel="noopener"><code>posit-dev-skills</code></a> plugin includes an LLM-optimised mirai guide for AI coding assistants.</li>
</ul>
<p>Issues, feedback, and questions are very welcome on GitHub: <a href="https://github.com/r-lib/mirai/issues" target="_blank" rel="noopener">r-lib/mirai</a>, <a href="https://github.com/r-lib/nanonext/issues" target="_blank" rel="noopener">r-lib/nanonext</a>, <a href="https://github.com/shikokuchuo/mori/issues" target="_blank" rel="noopener">shikokuchuo/mori</a>.</p>
<h2 id="acknowledgments">Acknowledgments
</h2>
<p>A big thanks to everyone who contributed to mirai 2.7.0, mori 0.2.0, and nanonext 1.9.0 through their issues, pull requests, and discussions: <a href="https://github.com/HenrikBengtsson" target="_blank" rel="noopener">@HenrikBengtsson</a>, <a href="https://github.com/kentqin-cve" target="_blank" rel="noopener">@kentqin-cve</a>, <a href="https://github.com/king-of-poppk" target="_blank" rel="noopener">@king-of-poppk</a>, <a href="https://github.com/manforkr" target="_blank" rel="noopener">@manforkr</a>, <a href="https://github.com/mcol" target="_blank" rel="noopener">@mcol</a>, <a href="https://github.com/michaelmayer2" target="_blank" rel="noopener">@michaelmayer2</a>, <a href="https://github.com/pmac0451" target="_blank" rel="noopener">@pmac0451</a>, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>, and <a href="https://github.com/wlandau" target="_blank" rel="noopener">@wlandau</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-12_production-async-r/featured.jpg" length="261721" type="image/jpeg" />
    </item>
    <item>
      <title>Positron May Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/</link>
      <pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources.</p>
<h2 id="product-updates">Product updates
</h2>
<h3 id="announcing-the-packages-pane">Announcing the Packages pane
</h3>
<p>Our <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages pane</a> is available in this release as a preview feature. The new Packages pane brings streamlined package management directly into the IDE so you can see at a glance what&rsquo;s installed, what&rsquo;s attached, and what&rsquo;s out of date.</p>
<img src="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/packages-pane-attached.gif" data-fig-alt="The Positron Packages pane in the Primary Sidebar, showing installed packages with attach status and a search field; the user attaches a package and the row updates to show it as attached." />
<p>Find it in the Primary Sidebar by clicking the package icon. Positron automatically detects and integrates with your current interpreter; today we have support for pip, venv, uv, and conda for Python, plus base R, pak, and renv for R. You can search, sort, install, update, remove, filter by category (including &ldquo;Outdated Packages&rdquo;), and even see which packages are attached in your current session. To hide the pane, disable <a href="positron://settings/positron.packages.enable"><code>positron.packages.enable</code></a>. <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">Let us know</a> how it fits into your workflow.</p>
<h3 id="inline-output-for-quarto">Inline output for Quarto
</h3>
<p>Inline output for <code>.qmd</code> documents was one of Positron&rsquo;s most-requested features ever, with many users telling us it was the one thing keeping them tied to RStudio or Jupyter notebooks. We are happy to share that Quarto inline output is available this release as a preview feature.</p>
<img src="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/quarto-inline-output.png" data-fig-alt="A Quarto document open in Positron showing inline output beneath an executed code cell, with an execution status line indicating runtime." />
<p>Quarto kernels start automatically when you open a document with inline output, so you don&rsquo;t wait for a kernel on first execution. Each inline output cell shows a status line with execution status and elapsed time, and outputs are now collapsible when they get long. We&rsquo;ve also added a tool to switch the interpreter used for inline output, support for <em>Show Notebook Console</em> for Quarto documents, and fixed a handful of papercuts around execution indicators, autocompletion, and the kernel selector. Enable inline output via the <a href="positron://settings/positron.quarto.inlineOutput.enabled"><code>positron.quarto.inlineOutput.enabled</code></a> setting.</p>
<h3 id="extensions-available-from-posit-public-package-manager">Extensions available from Posit Public Package Manager
</h3>
<p>Positron&rsquo;s <a href="https://positron.posit.co/extensions" target="_blank" rel="noopener">extension gallery</a> now uses <a href="https://p3m.dev" target="_blank" rel="noopener">Posit Public Package Manager</a> (P3M) by default, replacing Open VSX as the source for browsing and installing extensions. The catalog is the same set of extensions you already use, served from Posit infrastructure for reliable distribution. If you prefer to stay on Open VSX, the new <a href="positron://settings/positron.extensions.gallerySource"><code>positron.extensions.gallerySource</code></a> setting lets you switch back at any time. For organizations on Posit Workbench, <a href="https://posit.co/blog/manage-vs-code-extensions-like-packages-with-posit-package-manager-2026-04-0/" target="_blank" rel="noopener">Posit Package Manager also added support for mirroring and serving VS Code extensions</a> from your own instance, bringing the same governance, security, and air-gapped distribution to extensions that you already have for R and Python packages.</p>
<h3 id="positron-notebook-editor-is-now-in-beta">Positron Notebook Editor is now in beta
</h3>
<p>The <a href="https://positron.posit.co/positron-notebook-editor" target="_blank" rel="noopener">Positron Notebook Editor</a> for <code>.ipynb</code> files is officially moving from alpha to beta.</p>
<img src="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/notebook-rstats.gif" data-fig-alt="Working with an R Jupyter notebook in the Positron Notebook Editor." />
<p>This release brings a wave of polish and reliability we hope makes it ready for your daily workflow:</p>
<ul>
<li>
<p><strong>Cleaner git diffs by default:</strong> New <a href="positron://settings/notebook.save.outputs"><code>notebook.save.outputs</code></a> and <a href="positron://settings/notebook.save.executionCounts"><code>notebook.save.executionCounts</code></a> settings let you exclude outputs and execution counts from saved notebook files so reviewers see only the relevant code changes.</p>
</li>
<li>
<p><strong>Stronger R support:</strong> R notebooks can now be debugged with breakpoints and the &ldquo;Debug Cell&rdquo; action, and R dataframe outputs in notebook cells now render in the inline Data Explorer for the same interactive exploration you get in the Console.</p>
</li>
<li>
<p><strong>Help on F1:</strong> Pressing <kbd>F1</kbd> on a function in a notebook cell now opens the <a href="https://positron.posit.co/help-pane" target="_blank" rel="noopener">Help pane</a>, just like in <code>.py</code> and <code>.R</code> files. Works for both Python and R Jupyter notebooks.</p>
</li>
<li>
<p><strong>UX polish:</strong> Long cell outputs default to scrolling instead of overwhelming the page, the cell action bar stays visible as you scroll through tall cells, markdown cells render footnotes, and your scroll position is preserved when switching between notebook tabs.</p>
</li>
</ul>
<h3 id="ai-chat-and-code-assistance">AI chat and code assistance
</h3>
<p>This release introduces two new AI options: Posit Assistant and Posit AI.</p>
<ul>
<li>
<p>You may have tried some of our previous experiments for AI code assistance available in Positron, Positron Assistant and/or Databot. We are now migrating to a more data-science-specific, holistic, unified approach to AI chat, built on the same Posit Assistant interface that <a href="https://posit.co/blog/introducing-ai-in-rstudio/" target="_blank" rel="noopener">we recently brought to RStudio</a>. We acknowledge that the number of different tools we&rsquo;ve made available and how their names have evolved have been pretty confusing, but this space is changing fast and our initial experiments have helped us understand the best ways to integrate AI into your data science workflow. With Posit Assistant, instead of switching between tools, you get code generation, next edit suggestions, chat, and agentic tools all in one place. <a href="https://posit-dev.github.io/assistant/docs/downloads/positron/" target="_blank" rel="noopener">Learn how to get started</a>.</p>
</li>
<li>
<p><a href="https://posit.co/products/ai" target="_blank" rel="noopener">Posit AI</a> is a new, optional model provider service available to use with Posit Assistant. Posit AI is a subscription priced at $20/month for individual users; both Positron and Posit Assistant remain free to use. <a href="https://posit.co/products/ai" target="_blank" rel="noopener">Learn how to sign up for an account</a>.</p>
</li>
</ul>
<p>This release also provides additional improvements to the broader Assistant experience. Each Assistant response now shows which model generated it, so you always know whether you&rsquo;re getting output from Claude, GPT-5, or another provider. We also improved the documentation and error handling for Microsoft Foundry and prompt caching for AWS Bedrock.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li><a href="https://opensource.posit.co/blog/2026-04-07_april-newsletter/" target="_blank" rel="noopener">Last release</a>, we announced <a href="https://opensource.posit.co/blog/2026-04-06_positron-server-jupyterhub/" target="_blank" rel="noopener">Positron Server</a> for academic use in JupyterHub, making it free to host Positron for teaching. We are now expanding support for Positron Server on Open OnDemand. If you are interested in using Positron for teaching, <a href="https://opensource.posit.co/blog/2026-04-06_positron-server-jupyterhub/" target="_blank" rel="noopener">follow the instructions</a> to get a teaching license and <a href="https://scheduler.zoom.us/cindy-tong/positron-education" target="_blank" rel="noopener">book time with our team</a> to discuss your use case.</li>
<li>We&rsquo;re prototyping first-class SQL editing and execution in Positron, including support for visualizations with <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release/" target="_blank" rel="noopener">ggsql</a>. We&rsquo;ll share more as this takes shape, and in the meantime, <a href="https://github.com/posit-dev/positron/issues/7233" target="_blank" rel="noopener">let us know</a> your thoughts and current pain points.</li>
<li>Join <a href="https://posit.co/webinar/compliance-without-friction-mastering-the-persistent-analysis-lifecycle" target="_blank" rel="noopener">our next webinar</a> on May 12 to learn how Positron, Workbench, Connect, and Package Manager work together in the reproducible analysis lifecycle.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/featured.svg" length="97354" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>Bringing OpenTelemetry to R in production</title>
      <link>https://opensource.posit.co/blog/2026-05-07_opentelemetry/</link>
      <pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-07_opentelemetry/</guid>
      <dc:creator>Charlie Gao</dc:creator>
      <dc:creator>Aaron Jacobs</dc:creator>
      <dc:creator>Barret Schloerke</dc:creator>
      <dc:creator>Gábor Csárdi</dc:creator><description><![CDATA[<p>We&rsquo;re bringing <a href="https://opentelemetry.io/" target="_blank" rel="noopener">OpenTelemetry</a> to R. As a Posit-wide initiative across our open source packages, we&rsquo;ve instrumented some of the most widely-used R packages for production workloads &ndash; <a href="https://shiny.posit.co/" target="_blank" rel="noopener">Shiny</a>, <a href="https://plumber2.posit.co/" target="_blank" rel="noopener">plumber2</a>, <a href="https://mirai.r-lib.org" target="_blank" rel="noopener">mirai</a>, <a href="https://httr2.r-lib.org" target="_blank" rel="noopener">httr2</a>, <a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>, <a href="https://pkg.yihui.org/knitr/" target="_blank" rel="noopener">knitr</a>, <a href="https://testthat.r-lib.org" target="_blank" rel="noopener">testthat</a> and <a href="https://dbi.r-dbi.org" target="_blank" rel="noopener">DBI</a>. You can add observability to your R applications with <strong>no code changes</strong>: set a few environment variables and you get traces, logs, and metrics flowing to the backend of your choice.</p>
<p>This is part of our commitment to R in production. As R applications scale &ndash; more users, more processes, more machines &ndash; you need tools to understand what&rsquo;s happening across your entire system. That&rsquo;s what OpenTelemetry is built for, and it&rsquo;s now available for R.</p>
<h2 id="what-is-opentelemetry">What is OpenTelemetry?
</h2>
<p><a href="https://opentelemetry.io/" target="_blank" rel="noopener">OpenTelemetry</a> (OTel) is a vendor-neutral, open source observability framework backed by the <a href="https://www.cncf.io/" target="_blank" rel="noopener">Cloud Native Computing Foundation</a>.
It has broad industry support across languages and platforms, and is already the standard in the Python, Java, JavaScript, and Go ecosystems. Now it&rsquo;s available for R.</p>
<p>OpenTelemetry defines a standard for collecting telemetry data:</p>
<ul>
<li><strong>Traces</strong> follow an operation as it moves through your system, showing exactly which functions ran, in what order, and how long each took.</li>
<li><strong>Metrics</strong> capture numerical measurements over time &ndash; things like request counts, response latencies, or memory usage.</li>
<li><strong>Logs</strong> record detailed events as they happen, providing context when you need to investigate a specific moment.</li>
</ul>
<p>The instrumented packages described in this post all use the <a href="https://otel.r-lib.org" target="_blank" rel="noopener">otel</a> package under the hood, and focus on traces, which provide the most immediate value for understanding production behavior. You can also use otel directly to add your own metrics and logs to your application code.</p>
<h2 id="why-observability-matters-for-r-in-production">Why observability matters for R in production
</h2>
<p>When you&rsquo;re developing interactively in RStudio or Positron, debugging is straightforward &ndash; you can step through code, inspect objects, and add print statements. But when your R code runs in production &ndash; a Shiny app serving hundreds of users, a plumber2 API handling thousands of requests, a batch pipeline running across a cluster &ndash; the picture changes.</p>
<p>The core concept in OpenTelemetry is a <strong>trace</strong>: the full path of a request through your system. Each trace is made up of <strong>spans</strong> &ndash; individual units of work with a name and a duration. Spans nest inside each other, so you can follow how each operation triggered the next:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-07_opentelemetry/otel-layers.svg"
      alt="A trace is made up of spans, each representing a unit of work. Nested spans show how operations cascade through your system." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>This structure gives you four things that are hard to get any other way:</p>
<ul>
<li><strong>Performance</strong>: Which part of a request is slow? Span durations pinpoint where time is spent &ndash; and nesting reveals unnecessary overhead.</li>
<li><strong>Errors</strong>: In development and testing, you know where errors are &ndash; you wrote the test, you control the inputs. In production, errors surface far from their root cause, across process boundaries and async operations, triggered by conditions you never anticipated. Traces show you the full chain of real operations that led to each failure, in the context where it actually happened.</li>
<li><strong>Centralized view</strong>: When your application extends across multiple R processes or machines &ndash; a Shiny app with mirai workers, or a plumber2 API behind a load balancer &ndash; traces are aggregated into a single view across all of them.</li>
<li><strong>Real-time monitoring</strong>: OTel is designed to be left on in production, not just enabled during testing or staging. With low overhead, it runs continuously &ndash; so you see what&rsquo;s happening as it happens, and dashboards and alerts catch problems before users report them.</li>
</ul>
<h2 id="instrumented-packages">Instrumented packages
</h2>
<p>We&rsquo;ve worked across teams to add OpenTelemetry instrumentation to the R packages where it matters most:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Package</th>
          <th style="text-align: left">Version</th>
          <th style="text-align: left">What it traces</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><a href="https://shiny.posit.co/" target="_blank" rel="noopener">Shiny</a></td>
          <td style="text-align: left">≥ 1.12.0</td>
          <td style="text-align: left">Session lifecycle, reactive updates, reactive expressions, background tasks</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://plumber2.posit.co/" target="_blank" rel="noopener">plumber2</a></td>
          <td style="text-align: left">≥ 0.2.0</td>
          <td style="text-align: left">API request handling, routing, endpoint execution</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://mirai.r-lib.org" target="_blank" rel="noopener">mirai</a></td>
          <td style="text-align: left">≥ 2.5.0</td>
          <td style="text-align: left">Task dispatch, daemon execution, results</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://httr2.r-lib.org" target="_blank" rel="noopener">httr2</a></td>
          <td style="text-align: left">≥ 1.2.2</td>
          <td style="text-align: left">HTTP requests and responses</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a></td>
          <td style="text-align: left">≥ 0.4.1</td>
          <td style="text-align: left">LLM API calls, tool execution, token usage</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://pkg.yihui.org/knitr/" target="_blank" rel="noopener">knitr</a></td>
          <td style="text-align: left">≥ 1.51</td>
          <td style="text-align: left">Document rendering, chunk evaluation</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://testthat.r-lib.org" target="_blank" rel="noopener">testthat</a></td>
          <td style="text-align: left">≥ 3.3.2</td>
          <td style="text-align: left">Test execution</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://dbi.r-dbi.org" target="_blank" rel="noopener">DBI</a></td>
          <td style="text-align: left">≥ 1.3.0</td>
          <td style="text-align: left">Database queries and connections</td>
      </tr>
  </tbody>
</table>
<p>Together, these packages cover the most common production R workloads: web applications, APIs, parallel computing, HTTP clients, AI/LLM tools, report rendering pipelines, CI test runs, and database access. Because the instrumentation is built into the packages themselves, you benefit from it automatically &ndash; no wrapper functions, nor modifications to existing code. If you already have a Shiny app or plumber2 API, your app will generate traces as soon as you enable OpenTelemetry. Your application code stays exactly as it is.</p>
<h2 id="seeing-it-in-action">Seeing it in action
</h2>
<p>To make this concrete, let&rsquo;s look at a Shiny chat app built with <a href="https://posit-dev.github.io/shinychat/" target="_blank" rel="noopener">shinychat</a> and <a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a> that fetches weather forecasts. It uses mirai for async execution and httr2 for weather API requests.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-07_opentelemetry/chat-demo.png"
      alt="The weather chat app uses Shiny, shinychat, ellmer, mirai, and httr2 &ndash; all instrumented with OpenTelemetry." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>A user asks about the weather in Atlanta and Newcastle:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-07_opentelemetry/chat-screenshot.png"
      alt="The running chat application, showing a successful weather query for Atlanta and a failed one for Newcastle." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>With OpenTelemetry enabled, every step is captured automatically. Here are the traces from those two queries:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-07_opentelemetry/otel-traces.png"
      alt="Traces from the weather chat app, showing the full chain of operations from Shiny session through to HTTP requests. The second request shows an error (red) that&rsquo;s immediately visible in the trace." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>The trace reveals the full chain of operations from user input through to the HTTP request, across process boundaries, with no manual logging. The nesting shows how each step triggered the next, and the durations show where time was spent.</p>
<p>The second query failed. Without tracing, you&rsquo;d see an error in your logs and start investigating. Here, the failure is immediately visible &ndash; the red span pinpoints where it occurred and the surrounding context shows why. In a production system with many concurrent users, that&rsquo;s the difference between minutes and seconds of debugging.</p>
<h2 id="getting-started">Getting started
</h2>
<p>Getting started requires the otelsdk package and a few environment variables. No changes to your application code.</p>
<p>Here&rsquo;s how the pieces fit together: the instrumented R packages generate telemetry data as they run. The <a href="https://otelsdk.r-lib.org" target="_blank" rel="noopener">otelsdk</a> package collects and exports this data over HTTP to a <strong>backend</strong> &ndash; a service that stores your traces and provides a web dashboard where you can search, filter, and visualize them (like the trace screenshots above).</p>
<h3 id="step-1-install-otelsdk">Step 1: Install otelsdk
</h3>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;otelsdk&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h3 id="step-2-choose-a-backend">Step 2: Choose a backend
</h3>
<p>OpenTelemetry is vendor-neutral, so you can send your data to any compatible backend:</p>
<ul>
<li><strong>Cloud services</strong>: <a href="https://logfire.pydantic.dev/" target="_blank" rel="noopener">Logfire</a>, <a href="https://grafana.com/products/cloud/" target="_blank" rel="noopener">Grafana Cloud</a>, <a href="https://langfuse.com/" target="_blank" rel="noopener">Langfuse</a></li>
<li><strong>Self-hosted</strong>: <a href="https://www.jaegertracing.io/" target="_blank" rel="noopener">Jaeger</a>, <a href="https://zipkin.io/" target="_blank" rel="noopener">Zipkin</a>, <a href="https://prometheus.io/" target="_blank" rel="noopener">Prometheus</a></li>
</ul>
<p>During development, you can also run an <a href="https://opentelemetry.io/docs/collector/" target="_blank" rel="noopener">OpenTelemetry Collector</a> on your own machine and inspect traces directly, without sending data anywhere else.</p>
<p>Each backend will give you an endpoint URL and an authentication token. The <a href="https://otelsdk.r-lib.org/reference/collecting.html#setup" target="_blank" rel="noopener">otelsdk collecting telemetry data guide</a> has examples for some common backends.</p>
<h3 id="step-3-set-environment-variables">Step 3: Set environment variables
</h3>
<p>Add these to your <code>.Renviron</code> file (use <code>usethis::edit_r_environ()</code> to open it), replacing the endpoint and token with the values from your chosen backend. This example uses <a href="https://logfire.pydantic.dev/" target="_blank" rel="noopener">Logfire</a>, which offers a free tier to get started:</p>
<div class="code-block"><pre tabindex="0"><code>OTEL_TRACES_EXPORTER=&#34;http&#34;
OTEL_EXPORTER_OTLP_ENDPOINT=&#34;https://logfire-eu.pydantic.dev&#34;
OTEL_EXPORTER_OTLP_HEADERS=&#34;Authorization=&lt;YOUR-WRITE-TOKEN&gt;&#34;</code></pre></div>
<p>If you&rsquo;re deploying content to Posit Connect, refer to <a href="https://docs.posit.co/connect/user/content-settings/#setting-env-vars" target="_blank" rel="noopener">how to set environment variables on Posit Connect</a>.</p>
<h3 id="step-4-run-your-app">Step 4: Run your app
</h3>
<p>That&rsquo;s it. Restart R, then run your Shiny app, plumber2 API, or any code that uses the instrumented packages. Traces will flow to your backend automatically. Open your backend&rsquo;s web dashboard to see them &ndash; you&rsquo;ll see a view like the trace screenshots shown above, with each span representing an operation in your application.</p>
<p>You can verify that tracing is active at any time:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">otel</span><span class="o">::</span><span class="nf">is_tracing_enabled</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] TRUE</span></span></span></code></pre></div></div>
<p>OpenTelemetry is designed to be safe in production. If anything goes wrong in the telemetry code itself, it will never crash your application &ndash; errors are silently suppressed so your app keeps running.</p>
<h2 id="zero-code-instrumentation">Zero-code instrumentation
</h2>
<p>Beyond the packages that ship with built-in instrumentation, otel supports <strong>zero-code instrumentation</strong> for any R package. Set the <code>OTEL_R_INSTRUMENT_PKGS</code> environment variable to a comma-separated list of package names, and otel will automatically create spans for their exported functions:</p>
<div class="code-block"><pre tabindex="0"><code>OTEL_R_INSTRUMENT_PKGS=dplyr,tidyr</code></pre></div>
<p>You can also fine-tune which functions are instrumented using include and exclude filters:</p>
<div class="code-block"><pre tabindex="0"><code>OTEL_R_INSTRUMENT_PKGS_DPLYR_INCLUDE=mutate,filter,select</code></pre></div>
<p>This is useful for adding visibility to any package in your stack, even those without built-in OTel support. See the <a href="https://otel.r-lib.org/reference/zci.html" target="_blank" rel="noopener">otel documentation</a> for full details.</p>
<h2 id="configuration-options">Configuration options
</h2>
<p>The otelsdk package is configured entirely through environment variables, following OpenTelemetry conventions:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Variable</th>
          <th style="text-align: left">Purpose</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>OTEL_TRACES_EXPORTER</code></td>
          <td style="text-align: left">Exporter type for traces (e.g. <code>&quot;http&quot;</code>)</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>OTEL_LOGS_EXPORTER</code></td>
          <td style="text-align: left">Exporter type for logs</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>OTEL_METRICS_EXPORTER</code></td>
          <td style="text-align: left">Exporter type for metrics</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>OTEL_EXPORTER_OTLP_ENDPOINT</code></td>
          <td style="text-align: left">URL of the OTLP-compatible backend</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>OTEL_EXPORTER_OTLP_HEADERS</code></td>
          <td style="text-align: left">Authentication headers for the backend</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>OTEL_R_INSTRUMENT_PKGS</code></td>
          <td style="text-align: left">Packages for zero-code instrumentation</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>OTEL_R_EMIT_SCOPES</code></td>
          <td style="text-align: left">Restrict telemetry to specific packages</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>OTEL_R_SUPPRESS_SCOPES</code></td>
          <td style="text-align: left">Exclude specific packages from telemetry</td>
      </tr>
  </tbody>
</table>
<p>See the <a href="https://otelsdk.r-lib.org/reference/environmentvariables.html" target="_blank" rel="noopener">otelsdk environment variables reference</a> for the complete list.</p>
<h2 id="looking-ahead">Looking ahead
</h2>
<p>With instrumentation built into the packages that R users already rely on, observability becomes something you can turn on, not something you have to build.</p>
<p>We&rsquo;re also integrating an OpenTelemetry collector into <a href="https://posit.co/products/enterprise/connect/" target="_blank" rel="noopener">Posit Connect</a>, giving you an end-to-end observability solution you can simply turn on or off.</p>
<p>OTel support across the ecosystem continues to expand. If you&rsquo;d like to learn more:</p>
<ul>
<li><a href="https://otel.r-lib.org" target="_blank" rel="noopener">otel package documentation</a> &ndash; the instrumentation API</li>
<li><a href="https://otelsdk.r-lib.org" target="_blank" rel="noopener">otelsdk package documentation</a> &ndash; the SDK for collecting and exporting telemetry</li>
<li><a href="https://opensource.posit.co/blog/2025-12-10_shiny-r-1.12/">Shiny 1.12 OTel blog post</a> &ndash; deep dive into Shiny&rsquo;s OpenTelemetry support</li>
<li><a href="https://opentelemetry.io/" target="_blank" rel="noopener">OpenTelemetry project</a> &ndash; the upstream standard</li>
</ul>
<p>We&rsquo;re excited about what this opens up for the R community. Whether you&rsquo;re running a Shiny dashboard for a small team, a plumber2 API serving thousands of requests, or a data pipeline distributed across a cluster &ndash; you now have the tools to see what&rsquo;s happening, in real time, with no code changes required.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-07_opentelemetry/featured.jpg" length="489929" type="image/jpeg" />
    </item>
  </channel>
</rss>
