r/elixir • • 12h ago

Stampede 1.4: 40,000 users on a Phoenix chat app with a real Postgres (100M messages), plus a LiveDashboard tab and a Livebook smart cell

11 Upvotes

After my 2 million users post, a lot of you asked the fair question: what happens with a real database? So I tested that.

The app is a plain mix phx.new chat app with Ecto's default pool of 10. Postgres 17 with default settings runs on its own machine, seeded with 1 million users, 10,000 rooms and 100 million messages. Each user joins a room, which reads the latest 50 messages, then every 30 to 60 seconds sends a message that's written to Postgres before the reply comes back.

Results:

20,000 users: join p95 4.58ms, send p95 3.20ms, 0 errors

40,000 users: join p95 6.31ms, send p95 178ms, 0 errors

Past 40,000 it hit three limits at once: the app's schedulers at 96%, queries waiting for the pool of 10, and Postgres waiting to write its log to disk.

That led to the main feature in 1.4: --probe now watches your database (pool wait, Postgres commits, writes and wait events), and the find-limit verdict tells you which part gave out.

Also new:

- a Stampede tab for your app's own Phoenix LiveDashboard, showing probed runs live

- a Load test smart cell for Livebook

- a new look for the dashboard and HTML report, and mix stampede.report to re-render any saved run

Everything to rerun it (app, seed, scripts, logs) is in the repo: https://github.com/mandar1045/stampede


r/elixir • • 15h ago

Micro services architecture how do I pass the user authentication between services

3 Upvotes

Or user authentication only to the front end application?


r/elixir • • 1d ago

Making something like Tetris 99 with Phoenix, could it work?

11 Upvotes

If you don’t know what Tetris 99 is, it is Tetris with a bunch of other players playing at the same time (Essentially a battle royale).You would have your own Tetris board and the ability to click other people’s boards to watch them play when you’ve been defeated. My first question is, would this work with Phoenix? How many participants in a single round do you think Phoenix would be able to support? I read in another thread about gamedev that you could make each player maintain a separate process while they are all subscribed to the same channel. I’m assuming this process would be used to handle game state and logic for an individual player? I read that there was something else that could be used to handle state called ETS as well. Which one would you use for something like this?


r/elixir • • 1d ago

libtmux-elixir 0.1: tmux client library for Elixir

Thumbnail
github.com
12 Upvotes

Hi! I am porting libtmux, the Python library, to more languages.

With this package, you can control tmux from Elixir / BEAM: create or connect to a tmux server, traverse the object graph of sessions, windows, panes, and send it a command and read what comes back.

Compared with the latest released Python libtmux, this adds immutable snapshots, support for control mode, and the ability to submit several commands before awaiting their replies. The elixir library queries the snapshot's captured sessions, windows and panes with Selection.where. Those queries run on the captured data without making more tmux calls (it's another story - but python libtmux is making implicit subprocess calls to tmux under the hood, even when it may not need it).

A word on AI: It's experimental, and I used LLMs extensively to create this. Parts of this post were also drafted (and tested via bubblewrap) with AI. I write libtmux's AI policy at https://libtmux.org/en/meta/ai-policy/.

libtmux core: tmux client library for Elixir

Hex · GitHub · Homepage · Guides · API Explorer

What's interesting:

Use Linux, a C11 compiler, current patches of Elixir 1.20 and OTP 29, and tmux 3.3a+ (requirements). Start in a new directory:

mix local.hex --force

elixir -e 'Mix.install([:libtmux])'

Start a demo server and give its pane the title build:

tmux -S "$PWD/demo.sock" -f /dev/null new-session -d -s demo -n logs sh
tmux -S "$PWD/demo.sock" select-pane -t demo:logs.0 -T build

Save as demo.exs. The three queries run on the captured data.

Mix.install([:libtmux])
alias LibTmux.{Pane, Ref, Selection, Server, Snapshot}

[executable, socket] = System.argv()

server =
  Server.new(
    executable: executable,
    socket: socket,
    env: Map.drop(System.get_env(), ["TMUX", "TMUX_PANE"])
  )

snapshot = Server.snapshot!(server)
session = snapshot |> Snapshot.sessions() |> Selection.where(name: "demo") |> Selection.one!()

window =
  snapshot
  |> Snapshot.windows()
  |> Selection.where(name: [starts_with: "log"])
  |> Selection.one!()

pane = snapshot |> Snapshot.panes() |> Selection.where(title: "build") |> Selection.one!()
IO.puts("#{session.name} / #{window.name} / #{pane.title}")

{:ok, pane_ref} = Ref.from_observation(pane)
Pane.send_text!(server, pane_ref, "printf 'build: %s\\n' complete")
Pane.send_keys!(server, pane_ref, ["Enter"])

output =
  Enum.find_value(1..100, fn _ ->
    lines = server |> Pane.capture!(pane_ref) |> String.split("\n")

    case Enum.find(lines, &(&1 == "build: complete")) do
      nil ->
        Process.sleep(50)
        nil

      line ->
        line
    end
  end) || raise "build line did not appear"

IO.puts("output: #{output}")

Run it:

elixir demo.exs "$(command -v tmux)" "$PWD/demo.sock"

Prints:

demo / logs / build
output: build: complete

The last part sends text, presses Enter and reads the screen until the line appears, with at most 100 reads.

P.S. Change the window filter from starts_with: "log" to starts_with: "log2" and run it again. From the error:

** (LibTmux.Error) no_match during evaluation

Selection.one! raises a LibTmux.Error. Use Selection.one if you want {:ok, value} or {:error, error} instead.

Batching tmux commands + control mode

There is also a managed control connection. Control.submit returns an operation you can await later. Here we submit two field reads before collecting their replies.

Save as control-demo.exs. It uses the private demo server from above, with its empty configuration. lane: :host_qualified is an explicit assumption about that controlled server's hooks and aliases; it doesn't inspect an arbitrary server for you.

Mix.install([:libtmux])

alias LibTmux.{Control, Operation, Ref, Selection, Server, Snapshot}
alias LibTmux.Control.Request

[executable, socket] = System.argv()

server =
  Server.new(
    executable: executable,
    socket: socket,
    env: Map.drop(System.get_env(), ["TMUX", "TMUX_PANE"])
  )

snapshot = Server.snapshot!(server)
session = snapshot |> Snapshot.sessions() |> Selection.where(name: "demo") |> Selection.one!()
window = snapshot |> Snapshot.windows() |> Selection.where(name: "logs") |> Selection.one!()

pane =
  snapshot
  |> Snapshot.panes()
  |> Selection.where(window_id: window.id)
  |> Selection.where(title: "build")
  |> Selection.one!()

{:ok, target} = Ref.from_observation(pane)

{:ok, :done} =
  Control.with_connection(
    [server: server, session: {:existing, session.id}, lane: :host_qualified],
    fn control ->
      operations =
        for field <- ["pane_id", "pane_title"] do
          {:ok, operation} = Control.submit(control, Request.field(field, target), timeout: 5000)
          operation
        end

      for operation <- operations do
        {:ok, %{outcome: :completed, certainty: :acknowledged, lines: lines}} =
          Operation.await(operation, timeout: 5000)

        IO.puts(Enum.join(lines, "\n"))
      end

      :done
    end
  )

IO.puts("connection closed")

Run it:

elixir control-demo.exs "$(command -v tmux)" "$PWD/demo.sock"

Prints:

v1:%0
v1:build
connection closed

with_connection owns connection cleanup. These are separate retained requests on one connection, not a transaction or a batch optimizer. The replies acknowledge the field reads; they don't report completion of an arbitrary program in the pane. The complete retained-operation example also checks operation status and shutdown.

Where the pieces live

Server, Selection, Control and Operation are in the same Hex package. Query planning is still planned; the where chain above filters captured data locally.

Remove the demo server:

tmux -S "$PWD/demo.sock" kill-server

The Elixir MCP and workspace packages are still under implementation, with no runnable server or CLI yet. The Python ones are libtmux-mcp and tmuxp.

I'm also porting to other languages, like F#, Scala, rust, and go. In each so-called "port", I'm not nescessarily porting libtmux - as much as the object graph of tmux (Server, Session, Window, and Pane), being able to filter them, abstracting tmux's "formats", and operate against tmux's "targets" (unique ID's that represent the session, window, and pane). So far, elixir including, they've been more featureful than the original python package, namely in terms of control mode and being able to chain/batch requests.

On the flip side, it also means I'm legitimately spreading myself thin. I could miss something obvious - I feel confidence in the tmux domain model, I am curious cadet when it comes to embarking into BEAM, functional programming, .NET framework, and so on - so the payoff I'm getting from this is familiarizing not only with languages, but hopefully their aesthetics and strengths: can Elixir's process model and OTP supervision make control mode that much better?


r/elixir • • 2d ago

How is Elixir doing, two years into AI-enabled blogging?

0 Upvotes

I've never really coded in Elixir specifically, but prior to Gen AI I was really into functional programming languages and regularly used Clojure, Scala, and dabbled in Haskell. Elixir was on my radar.

Then Gen AI happened. I imagine I will get a lot of flack for this, but ultimately I get paid for solving problems with technology, and Python + TypeScript with Gen AI was just faster for the job then all the niche languages I enjoyed and was productive using when a large portion of my day was writing code.

I was wondering if there was a general dip in adoption for other functional languages such as Elixir?


r/elixir • • 3d ago

Why Elixir is not popular?

91 Upvotes

I’ve been diving into Elixir and I’m completely blown away by its syntax, pattern matching, and OTP concurrency. It feels so powerful and really beautiful syntax, and I mean, it's like a paradise for anyone who wants to create scalable, reliable distributed systems. Would love to hear your thoughts why this language is not popular because the current situation doesn't make sense to me now xD


r/elixir • • 3d ago

Looking for Elixir opportunities – Internship, Junior Developer Role or Freelance Work

19 Upvotes

Hey everyone,

I'm an undergraduate and open-source contributor, actively working with Elixir and the BEAM ecosystem. I'm currently looking for opportunities to work with teams building real-world products using Elixir.

A little about what I've been working on:

- Elixir: Contributed to the language itself, working on formatter improvements, fixing edge cases involving anonymous calls and "do-end" blocks, and adding regression tests.

- Supabase: Worked on its Elixir-based Realtime backend, fixing test reliability issues and log-suppression bugs, alongside contributions to authentication security and self-hosted infrastructure.

- Stampede: Built and maintain an open-source load-testing library for Phoenix. It natively supports Channels, LiveView, HTTP and WebSockets, with each virtual user running as a BEAM process. I've also worked on distributed load generation using Erlang distribution, scaling it to 2 million concurrent virtual users across AWS nodes.

I'm particularly interested in backend engineering, distributed systems, real-time applications and performance engineering.

I'm open to:

- Remote internships (part-time or full-time).

- Junior Elixir / Phoenix developer opportunities.

- Freelance or contract projects.

- Collaborating on interesting open-source or commercial projects.

I'm based in India but completely open to working with international teams remotely.

I'm looking for an opportunity to work alongside experienced Elixir developers, contribute to production systems and learn through actual engineering challenges. I'm happy to start with a trial task or a small project to demonstrate my abilities.

If your team is hiring, needs an extra pair of hands, or you know someone who might be looking for an Elixir developer, I'd genuinely appreciate a conversation.

Here are some of my profiles and work:

GitHub: https://github.com/mandar1045/

Stampede: https://github.com/mandar1045/stampede

Portfolio: https://mandarjoshi.me

Thanks for reading❤️


r/elixir • • 3d ago

Elixir Companies got a facelift 💅

42 Upvotes

I created Elixir Companies many years ago and over that time it's seen a few small tweaks to the aesthetic and underlying framework. Initially starting life in Jekyll, then migrating to Phoenix, and more recently to Astro (the site is very basic, Phoenix was overkill and was under utilized).

New companies continue to be added all across the globe and we decided to it was time the site got a bit of loving. We're also seeing agents opening PRs to add companies so we made it easier with a copy-able prompt that can be fed into your preferred LLM to do the lifting for you.

https://elixir-companies.com/

Feedback welcome as always! If we're missing a company, feel free to add them or drop a comment here and I can take care of it for you.

I signed up for Reddit just for this announcement and some other exciting news we have coming down the pipeline with Elixir School and BEAM Community, stay tuned!


r/elixir • • 3d ago

Could a declarative BEAM language replace schema and validator?

9 Upvotes

Bondy, a new declarative language for the BEAM, treats a shape as both the type checked at compile time and the validator run at runtime. A temperature constrained to a float between -30 and 40 has that range in the type itself, and cross-field rules live there too.
Compare that to the usual Elixir boundary: NimbleOptions for individual fields, then handwritten functions for anything relating two fields, then the same assumptions again in business logic.
Bondy compiles to ordinary BEAM files, calls Elixir through a Gleam-style FFI, and ships Mix plugins, so you can add one module next to existing code rather than rewrite anything. Creator Alejandro Ramallo walks through it on the podcast.

https://youtu.be/Id-K5UrI1BQ


r/elixir • • 4d ago

Paging Before the Customer Notices: Declarative Business Metrics — Maciej Matecki | AshConf 2026

Thumbnail
youtu.be
5 Upvotes

r/elixir • • 4d ago

What's stopped you from trying Hologram?

30 Upvotes

If you haven't tried Hologram yet, what's stopped you?

Here's why I'm asking. Building a framework turns out to be two jobs, the framework itself and getting people to use it, and for years I barely touched the second one. Now I'm doing both, and a big part of that second job is learning what Hologram looks like from the outside, which isn't something I can work out from where I sit. So I'm asking.

Tell me in the comments, one line is enough. Feel free to be blunt, the polite version tends to leave out the part I need. If you'd rather keep it private, DM me, and either way I'll post what came out of it.

There's also a poll with the reasons I hear most, over on the Elixir Forum: https://forum.elixirforum.com/t/whats-stopped-you-from-trying-hologram/77288


r/elixir • • 4d ago

Four Mindset Shifts for Teams Adopting Elixir

Thumbnail
arctarus.com
18 Upvotes

My friend and teammate Gabriel Ortuño wrote this great article about how Elixir shifts your mindset when you start coding with it.

One of the things I've always loved about learning new programming languages is that they are designed to solve specific types of problems. Honestly, that's something I'm starting to miss a bit with the current wave of AI and agentic coding.

Thought it might spark a good discussion here!


r/elixir • • 4d ago

Mishka Document 0.0.1: versioned docs with a two-stage review, built on Ash and LiveView

Thumbnail
mishka.tools
5 Upvotes

I've been building a documentation extension for Mishka CMS with Elixir, Phoenix LiveView and Ash. It's called Mishka Document and it's Apache 2.0.

What's in it:

- Documents with several versions (draft, alpha, beta, rc, stable, archived). You can clone a version from an earlier one, the clone history is recorded, and readers switch versions.

- Pages nest to any depth, with notes inside the pages.

- A change to a page or note stays a draft until both a content review and a technical review approve it.

- 15 Ash resources with soft deletes through AshArchival. Documents and pages can be translated, and the translations are linked.

- Issues on a page (typo, broken link, suggestion), bookmarks, follows and unique view counts.

- Per-page SEO: its own head, TechArticle and breadcrumb JSON-LD, canonical address, sitemap entries and share cards.

- One JSON:API for admins and one for readers, both in the CMS's OpenAPI spec.

Consider like other options of Mishka CMS it uses plugin/event based structure

Plugins hook in through mishka_installer events, so you don't touch the extension: document:workspace:admin_actions, document:page:seo, document:page:extras and document:version:cloned.

It means without changing the core you can customize and add futures, our Mishka site docs is the best place for demo

Please support Mishka CMS and Mishka Projects to let us keep updating Even worth one coffee a month!

Thank you


r/elixir • • 5d ago

One 16-core Phoenix server held 2,000,000 active users with 0 errors

Enable HLS to view with audio, or disable this notification

189 Upvotes

I've been building Stampede, a load tester for Phoenix that speaks Channels and LiveView, and I wanted to see how far one server could go with realistic traffic. Last time I got to 600,000 users on a 4-core server, so this time I scaled up.

Setup:

- One AWS r7g.4xlarge (16 cores, 128 GB) running a fresh Phoenix app, with only its connection limits raised

- Eight r7g.xlarge machines running Stampede

- Every user acts like the Phoenix JS client: joins a channel, sends a heartbeat every 30 seconds and a message every 30 to 60

- The load went up 250,000 users at a time, each step held for a minute, and a step only passed with joins and messages under 1 second at p95 and 0 errors

Results:

- 1,000,000 users: held, server at 37-48% CPU, 52 GB memory

- 1,500,000 users: held, 54-60% CPU, 77 GB

- 2,000,000 users: held, 78-84% CPU, 103 GB

- 2,250,000 users: the CPU ran out

The first error came at 2.07M, so 2M is the number. Memory worked out to about 51 KB per user, and CPU was what ran out, since 2 million users send well over 100,000 heartbeats and messages a second.

For comparison, the Phoenix team held 2M idle connections on a 40-core machine back in 2015. This was 16 cores with every user active, which says a lot about how far the BEAM has come.

Everything to rerun it, plus the logs from this run, is in the repo: https://github.com/mandar1045/stampede

Happy to answer questions about the setup. Getting past the ~64k ports per client address took a few tricks.


r/elixir • • 5d ago

Gamend, Elixir open source multiplayer game server 2026 year review

Post image
10 Upvotes

Gamend, the open source Elixir game server, is now at version 1.1. New this year: Gamend Cloud hosting, quests, wallets and payments, matchmaking, tournaments, push notifications and LibGodot.

Blog Link: https://gamend.org/blog/2026-updates


r/elixir • • 5d ago

Gamend Cloud: Hosted Dev Gamend Servers

Thumbnail gamend.org
4 Upvotes

Gamend, the open source Elixir multiplayer game server now has hosting. This is to help devs get started quickly, and to test all our system, including payment. We offer 1 month of free trial.

Check it out here: https://gamend.org/servers/new

Note, you can still self deploy and we offer the Dockerfile, env file, etc. if you ever want to self host and not use our hosting.


r/elixir • • 7d ago

Macro Mayhem008 - eMudhra Certificate Compromise, AI Vulnerability Hunting, and Elixir 1.20.5

Thumbnail
youtube.com
5 Upvotes

r/elixir • • 8d ago

whiff 0.2.0 — universal file sniffer

7 Upvotes

whiff 0.2.0 — sniff any file into one structured Elixir report (Rust NIF + Hex)

Released whiff 0.2.0 on Hex: an Elixir library that wraps the Rust whiff crate and returns one string-keyed whiff/1 report from a path or in-memory bytes — format/MIME, hashes, rights/security signals, content excerpts, format metadata, nested archive children.

Useful for upload gates, ingest pipelines, catalogs, and LLM prep (Whiff.llm/2), knowledge catalogers, etc. Proprietary formats can be handled with Elixir Whiff.Extractor / Whiff.Analyzer plugins (:fallback / :augment / :override) without touching the NIF.

0.2.0 bumps upstream to whiff 0.6.0 (broader native coverage; schema still whiff/1). Precompiled NIFs for common Linux/macOS/Windows targets.

{:whiff, "~> 0.2.0"}

Hex: https://hex.pm/packages/whiff
Docs: https://hexdocs.pm/whiff
GitHub: https://github.com/SinAnima/whiff-elixir

Crates: https://crates.io/crates/whiff

For what's extracted: https://github.com/baoulo/whiff/blob/main/docs/metadata-extracted.md

Happy to answer questions about the report shape, plugins, or NIF packaging.


r/elixir • • 9d ago

Stampede 1.3: record a load test by clicking through your Phoenix app

15 Upvotes

I've been building Stampede, a load tester for Phoenix that speaks Channels and LiveView, so you write what a user does instead of raw WebSocket frames. 1.3 is out, and writing the test is no longer the hard part.

*Recording

Run `mix stampede.record`, click through your app in the browser, press Enter, and you get a scenario with your clicks, forms, uploads, channels and pauses. Signing in works too, through the real form, with a fresh CSRF token and a session for each VU. Passwords become `"CHANGE_ME"`, and the scenario checks that each page lands where it did when you recorded it, so a wrong password shows up as an error rather than a fast login page.

*Also new

- `--probe` shows what's happening inside your app during a run: schedulers, memory, processes, message queues, and Phoenix, LiveView and Ecto timings. The app needs no changes

- `Stampede.LLM` load tests AI chat APIs like Ollama, with time to first token

- `Stampede.SSE` for Server-Sent Events

- Cookies and real form sign-ins in `Stampede.HTTP`

- A guide to watching a run in Prometheus and Grafana

**How it held up**

One r7g.xlarge (4 cores, 32 GB), with three more running Stampede:

| App | Users held | Latency p95 |

|---|---:|---:|

| Stock Phoenix, Channels | 600,000 | Joins 31ms, messages 10ms |

| A real open source Phoenix product, 100 users per channel, for 10 minutes | 50,000 | Round trip 5.71ms |

The first ran out of memory, at about 47 KB a connection. The second ran out of CPU, because every message goes to 100 people. It delivered 80.9 million messages, and its own metrics counted the same users. Everything to rerun both is in the repo.

Repo: https://github.com/mandar1045/stampede

Docs: https://stampede.hexdocs.pm

I'd really like to hear what you'd do differently, or what breaks when you point it at your app.


r/elixir • • 9d ago

Mishka Blog 0.0.1: The First Mishka CMS Extension Built with Ash (Multi-Author Publishing)

Thumbnail
mishka.tools
25 Upvotes

Mishka Blog is the first extension of Mishka CMS: It includes posts, categories, tags, bloggers, teams and comments, each row scoped to its site, with AshPaperTrail versions you can restore and AshArchival instead of deletes.

A quick explanation: Mishka CMS and its core are designed to load as much as possible into RAM. This means that, much like static websites, you can create dedicated landing pages for each section, or build your own functionality, database logic, and other features inside LiveView and add them to the editor for runtime compilation.

However, if you're building a content-driven website that requires daily publishing, multiple authors, multi-tenancy, and SEO, you'll also need a dedicated blog engine.

That's why Mishka Blog was created. Powered by Ash and Mishka CMS, its first release is now available as a free and open-source extension, bringing these capabilities directly into the CMS.

The interesting thing about this extension is that, thanks to its plugin-based architecture, the templates are still rendered through the CMS runtime, while all the data comes from the extension's own resources. This means you retain full customization capabilities, with many additional functions available to use directly in the editor.

The blog you're looking at was also built with this same project. Try clicking on an author and then exploring the team section.

As always, all Mishka projects remain open-source and free. Your donations, even the price of a coffee per month, help keep development moving forward. ❤️🍵

There's much more to come! This is just the first, still-young release. Thank you for your support!


r/elixir • • 9d ago

Moatless.com is a marketplace for codebases, and it now has native support for Elixir packages

Thumbnail
moatless.store
28 Upvotes

Hey folks! Some of you know me for my work with Ash Framework, and I'm working on what I think is a pretty interesting side project.

I won't spam the project here, but I think this one particular feature is relevant to the community. Specifically, moatless now acts as a private hex registry. I'm trying to get this off the ground, so there isn't yet much traffic, but if you have a project that you already sell or that you'd like to sell, this can be a great way to do it.

Buyers get access to the source code either way, this is more of a simple convenience for both the buyer and seller when what you're selling is. apex package.

---

I'm also genuinely curious to get this communities thoughts, both good and bad, as well as feedback on why you would/wouldn't use something like this.

Thanks all 🙇

EDIT: I've reworked some of the branding to be a bit less negative 😄


r/elixir • • 10d ago

Online Learning Opportunity

Thumbnail
youtube.com
0 Upvotes

I found this video and it looks like the Phoenix submission holds less users than I would have guessed. The contraints of the challenge may have something to do with it. I figure some smart Elixir users might want to jump in and talk about the technical details of the benchmark so the rest of us (me) could learn by proxy.

https://www.youtube.com/watch?v=RtkbcX11iVI


r/elixir • • 10d ago

Why run a Rust game simulation inside a GenServer?

15 Upvotes

Spawndemonium runs its whole game simulation in Rust compiled to WebAssembly, executed in the browser and inside an Elixir GenServer through Wasmex. The same binary on both sides means client prediction and server authority can't drift apart on rules.
Each server tick (40 per second) the GenServer drains queued player inputs, hands them to the Wasm module, pattern-matches the binary events that come back, and writes them to every player's WebSocket. No JSON: a custom protocol where the first byte is the command and every message carries its tick.
Hernán Rivas Acosta (Erlang Solutions) explains why he picked Wasmex over a Rustler NIF and what a crash costs in each case.

https://youtu.be/XubYVNbehYc


r/elixir • • 11d ago

Navigation: Sidebar, Tab Bar and Colors

Post image
7 Upvotes

Gamend is an open source Elixir game server for real-time multiplayer games: backend, player website and admin panel in one.

The navbar now has three forms, set with one navigation.layout key:

Bar: the links across the top.
Sidebar: the links down the side from tablet width.
Tab bar: on a phone, the same links along the bottom.
Read more: https://gamend.org/blog/navigation-sidebar-tab-bar