Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

I have a vanilla Phoenix LiveView project. This project has grown and now it has come to a point where I need to add a progress bar to display the progress of a long operation.

Now, LiveView comes with a minimalist CSS framework called milligram. This framework has no support for progress bars afaik. It also has no JavaScript.

Problem

The way I see it, have 3 options:

  1. start adding custom JavaScript scripts
  2. add a JavaScript library (like jQuery)
  3. add a JavaScript/CSS library (no idea which one to use here)

I frown upon option 1, mainly because for this specific use case, it means I will be re-inventing the wheel. I am fairly sure that progress bars are an issue that was solved long ago.

In regards to options 2 and 3, I am a little bit lost. I don’t know where I would add a JavaScript framework to Phoenix, nor do I know if doing so is recommended, as it may cause issues with LiveView’s native JavaScript code.

Questions

What I am looking for is the general consensus when it comes to LiveView apps:

  • Do you add JavaScript libraries to your projects, or do you prefer going with JavaScript/CSS frameworks?
  • If so, how/where do you put these? In which folders/files?
  • What are the safest (the ones that cause less issues) libraries to use with LiveView?
  • Which library/framework would you recommend ?

Please do let me know !

Showing Posts 11 to 20

derek-zhou

derek-zhou

Nice, I didn’t know that. I always use a colored div inside another uncolored div with dynamic width set by lv.

malloryerik

malloryerik

The Pragmatic Studio tutorial is solid and has good examples. Here’s a progress bar of sorts that they explain in the free sections of their course.

See a live demo of the progress bar-ish thing. I think you’ll see that this fits perfectly with the progress HTML element that @al2o3cr has suggested.

Looks like this btw:

As for JS in LiveView more generally, I personally still sprinkle in some Alpine here and there for some added development speed but you really don’t need it. Much more important is LiveView JS Commands that let you do common things, and LiveView Hooks as well as event listeners like window.addEventListener which along with JS.push and JS.dispatch let you create custom events (in bold because, well, if you really take in that section of the docs you’ll see why). So in this way LiveView has a kind of semi-prepared-foods counter of a JavaScript framework already baked in you can toss into the cooker with some enduring standards-based vanilla JS et voilà dinner is served.


Hmmm, did (does?) the (supposedly) late Anthony Balducci not bear a suspicious resemblance to Chris McCord? Birds aren’t real – Huh wait, maybe… is that why Aston always seems so into this longevity stuff? Balducci wanted to go on with life but needed a clean break. Because of his gourmet grocery skills he had access to all of the top stem cell scientists in the NYC area and was made “young again” – haven’t you noticed how McCord seems wise for his years? And just as Balducci invented gourmet groceries that were frameworks for delicious meals, he now under his new identity fires up a framework for gourmet webapps, which “just came to him” while he was munching gourmet chocolate-covered coffee beans JUST LIKE THOSE YOU CAN BUY AT BALDUCCI’S, he came up with Phoenix, Phoenix, Phoenix, THE Phoenix, The PHOENIX which rises again from the ashes ahem… omg the soup thickens and yes, Elixir is the alchemical agent, Phoenix an alchemical precursor to it, to… to the Elixir of life eternal!!! Call Liebnitz, Frege and Boole, this logic is unimpeachable, something is cooking in the state of Denmark!

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

@malloryerik The main issue I have with that course, is that it uses an old version of Phoenix LV. The newer versions do not have tailwind, and come instead with milligram.

The difference is so massive, that I had to remake my entire project, from zero, to use milligram.
Unfortunately for me, it looks like absolutely no one in the planet is using milligram, so tutorials and guides using it with LV are close to non-existent.

I am using the HTML progress element (as suggested by @LostKobrakai and @al2o3cr ), which seems to work just fine for the time being. Now I need to polish it into something that wont make my user’s eyes bleed :smiley:

lud

lud

Why rewrite your entire project for milligram? What prevents you to use tailwind or whatever with the last LiveView version ? I do not think that LiveView has anything to do with CSS, you should be able to use whatever you want.

LostKobrakai

LostKobrakai

Milligram afaik was chosen to be the least intrusive to the generated html, or in other words the easierst to remove from a phoenix project, while still given reasonable well styling out of the box. Personally I’d never leave this in any project becoming serious.

cblavier

cblavier

We’re using Tailwind and our own home-made library to help us at writing liveview components :

https://github.com/cblavier/phx_component_helpers

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

The were a bunch of NPM and Node issues that basically forced the re-write of the project. Tailwind and SASS were both the final nail in the coffin.

What would you replace it?
Any guides/tutorials you would recommend?

kardiojack

kardiojack

Has anyone used Stimulus JS in place of Alpine? From their Github repo it seems like Stimulus was designed specifically for adding javascript “sparkle” to MVC apps, Rails in Stimulus’ case but still.

Stimulus

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

After more research, I came to the conclusion that the progress HTML tag was not as customizable as I wanted.
So I decided to do some research for a CSS/HTML solution compatible with vanilla milligram.

I found none.

But my research was not fruitless. Based on all the things I had learned, I came up with this HTML/CSS progress bar that you can use with vanilla milligram CSS.

CSS:

:root {
    --darkpurple: #42389d;
    --lightpurple: #6961b3;
}

/* Progress bar */
.wrap-circles {
    display: grid;
    justify-content: center;
    align-items: center;
    padding: 30%;
}

.wrap-circles p {
    text-align: center;
    font-weight: bold;
    font-size: 1.1em;
}


.circle {
    position: relative;
    width: 9.375em;
    height: 9.375em;
    margin: auto;
    border-radius: 50%;
    background: var(--darkpurple);
    overflow: hidden;
}

.circle.per-0 {
    background-image: conic-gradient(var(--darkpurple) 0%, var(--lightpurple) 0);
}

.circle.per-1 {
    background-image: conic-gradient(var(--darkpurple) 1%, var(--lightpurple) 0);
}

.circle.per-2 {
    background-image: conic-gradient(var(--darkpurple) 2%, var(--lightpurple) 0);
}

.circle.per-3 {
    background-image: conic-gradient(var(--darkpurple) 3%, var(--lightpurple) 0);
}

.circle.per-4 {
    background-image: conic-gradient(var(--darkpurple) 4%, var(--lightpurple) 0);
}

.circle.per-5 {
    background-image: conic-gradient(var(--darkpurple) 5%, var(--lightpurple) 0);
}

.circle.per-6 {
    background-image: conic-gradient(var(--darkpurple) 6%, var(--lightpurple) 0);
}

.circle.per-7 {
    background-image: conic-gradient(var(--darkpurple) 7%, var(--lightpurple) 0);
}

.circle.per-8 {
    background-image: conic-gradient(var(--darkpurple) 8%, var(--lightpurple) 0);
}

.circle.per-9 {
    background-image: conic-gradient(var(--darkpurple) 9%, var(--lightpurple) 0);
}

.circle.per-10 {
    background-image: conic-gradient(var(--darkpurple) 10%, var(--lightpurple) 0);
}

.circle.per-11 {
    background-image: conic-gradient(var(--darkpurple) 11%, var(--lightpurple) 0);
}

.circle.per-12 {
    background-image: conic-gradient(var(--darkpurple) 12%, var(--lightpurple) 0);
}

.circle.per-13 {
    background-image: conic-gradient(var(--darkpurple) 13%, var(--lightpurple) 0);
}

.circle.per-14 {
    background-image: conic-gradient(var(--darkpurple) 14%, var(--lightpurple) 0);
}

.circle.per-15 {
    background-image: conic-gradient(var(--darkpurple) 15%, var(--lightpurple) 0);
}

.circle.per-16 {
    background-image: conic-gradient(var(--darkpurple) 16%, var(--lightpurple) 0);
}

.circle.per-17 {
    background-image: conic-gradient(var(--darkpurple) 17%, var(--lightpurple) 0);
}

.circle.per-18 {
    background-image: conic-gradient(var(--darkpurple) 18%, var(--lightpurple) 0);
}

.circle.per-19 {
    background-image: conic-gradient(var(--darkpurple) 19%, var(--lightpurple) 0);
}

.circle.per-20 {
    background-image: conic-gradient(var(--darkpurple) 20%, var(--lightpurple) 0);
}

.circle.per-21 {
    background-image: conic-gradient(var(--darkpurple) 21%, var(--lightpurple) 0);
}

.circle.per-22 {
    background-image: conic-gradient(var(--darkpurple) 22%, var(--lightpurple) 0);
}

.circle.per-23 {
    background-image: conic-gradient(var(--darkpurple) 23%, var(--lightpurple) 0);
}

.circle.per-24 {
    background-image: conic-gradient(var(--darkpurple) 24%, var(--lightpurple) 0);
}

.circle.per-25 {
    background-image: conic-gradient(var(--darkpurple) 25%, var(--lightpurple) 0);
}

.circle.per-26 {
    background-image: conic-gradient(var(--darkpurple) 26%, var(--lightpurple) 0);
}

.circle.per-27 {
    background-image: conic-gradient(var(--darkpurple) 27%, var(--lightpurple) 0);
}

.circle.per-28 {
    background-image: conic-gradient(var(--darkpurple) 28%, var(--lightpurple) 0);
}

.circle.per-29 {
    background-image: conic-gradient(var(--darkpurple) 29%, var(--lightpurple) 0);
}

.circle.per-30 {
    background-image: conic-gradient(var(--darkpurple) 30%, var(--lightpurple) 0);
}

.circle.per-31 {
    background-image: conic-gradient(var(--darkpurple) 31%, var(--lightpurple) 0);
}

.circle.per-32 {
    background-image: conic-gradient(var(--darkpurple) 32%, var(--lightpurple) 0);
}

.circle.per-33 {
    background-image: conic-gradient(var(--darkpurple) 33%, var(--lightpurple) 0);
}

.circle.per-34 {
    background-image: conic-gradient(var(--darkpurple) 34%, var(--lightpurple) 0);
}

.circle.per-35 {
    background-image: conic-gradient(var(--darkpurple) 35%, var(--lightpurple) 0);
}

.circle.per-36 {
    background-image: conic-gradient(var(--darkpurple) 36%, var(--lightpurple) 0);
}

.circle.per-37 {
    background-image: conic-gradient(var(--darkpurple) 37%, var(--lightpurple) 0);
}

.circle.per-38 {
    background-image: conic-gradient(var(--darkpurple) 38%, var(--lightpurple) 0);
}

.circle.per-39 {
    background-image: conic-gradient(var(--darkpurple) 39%, var(--lightpurple) 0);
}

.circle.per-40 {
    background-image: conic-gradient(var(--darkpurple) 40%, var(--lightpurple) 0);
}

.circle.per-41 {
    background-image: conic-gradient(var(--darkpurple) 41%, var(--lightpurple) 0);
}

.circle.per-42 {
    background-image: conic-gradient(var(--darkpurple) 42%, var(--lightpurple) 0);
}

.circle.per-43 {
    background-image: conic-gradient(var(--darkpurple) 43%, var(--lightpurple) 0);
}

.circle.per-44 {
    background-image: conic-gradient(var(--darkpurple) 44%, var(--lightpurple) 0);
}

.circle.per-45 {
    background-image: conic-gradient(var(--darkpurple) 45%, var(--lightpurple) 0);
}

.circle.per-46 {
    background-image: conic-gradient(var(--darkpurple) 46%, var(--lightpurple) 0);
}

.circle.per-47 {
    background-image: conic-gradient(var(--darkpurple) 47%, var(--lightpurple) 0);
}

.circle.per-48 {
    background-image: conic-gradient(var(--darkpurple) 48%, var(--lightpurple) 0);
}

.circle.per-49 {
    background-image: conic-gradient(var(--darkpurple) 49%, var(--lightpurple) 0);
}

.circle.per-50 {
    background-image: conic-gradient(var(--darkpurple) 50%, var(--lightpurple) 0);
}

.circle.per-51 {
    background-image: conic-gradient(var(--darkpurple) 51%, var(--lightpurple) 0);
}

.circle.per-52 {
    background-image: conic-gradient(var(--darkpurple) 52%, var(--lightpurple) 0);
}

.circle.per-53 {
    background-image: conic-gradient(var(--darkpurple) 53%, var(--lightpurple) 0);
}

.circle.per-54 {
    background-image: conic-gradient(var(--darkpurple) 54%, var(--lightpurple) 0);
}

.circle.per-55 {
    background-image: conic-gradient(var(--darkpurple) 55%, var(--lightpurple) 0);
}

.circle.per-56 {
    background-image: conic-gradient(var(--darkpurple) 56%, var(--lightpurple) 0);
}

.circle.per-57 {
    background-image: conic-gradient(var(--darkpurple) 57%, var(--lightpurple) 0);
}

.circle.per-58 {
    background-image: conic-gradient(var(--darkpurple) 58%, var(--lightpurple) 0);
}

.circle.per-59 {
    background-image: conic-gradient(var(--darkpurple) 59%, var(--lightpurple) 0);
}

.circle.per-60 {
    background-image: conic-gradient(var(--darkpurple) 60%, var(--lightpurple) 0);
}

.circle.per-61 {
    background-image: conic-gradient(var(--darkpurple) 61%, var(--lightpurple) 0);
}

.circle.per-62 {
    background-image: conic-gradient(var(--darkpurple) 62%, var(--lightpurple) 0);
}

.circle.per-63 {
    background-image: conic-gradient(var(--darkpurple) 63%, var(--lightpurple) 0);
}

.circle.per-64 {
    background-image: conic-gradient(var(--darkpurple) 64%, var(--lightpurple) 0);
}

.circle.per-65 {
    background-image: conic-gradient(var(--darkpurple) 65%, var(--lightpurple) 0);
}

.circle.per-66 {
    background-image: conic-gradient(var(--darkpurple) 66%, var(--lightpurple) 0);
}

.circle.per-67 {
    background-image: conic-gradient(var(--darkpurple) 67%, var(--lightpurple) 0);
}

.circle.per-68 {
    background-image: conic-gradient(var(--darkpurple) 68%, var(--lightpurple) 0);
}

.circle.per-69 {
    background-image: conic-gradient(var(--darkpurple) 69%, var(--lightpurple) 0);
}

.circle.per-70 {
    background-image: conic-gradient(var(--darkpurple) 70%, var(--lightpurple) 0);
}

.circle.per-71 {
    background-image: conic-gradient(var(--darkpurple) 71%, var(--lightpurple) 0);
}

.circle.per-71 {
    background-image: conic-gradient(var(--darkpurple) 71%, var(--lightpurple) 0);
}

.circle.per-72 {
    background-image: conic-gradient(var(--darkpurple) 72%, var(--lightpurple) 0);
}

.circle.per-73 {
    background-image: conic-gradient(var(--darkpurple) 73%, var(--lightpurple) 0);
}

.circle.per-74 {
    background-image: conic-gradient(var(--darkpurple) 74%, var(--lightpurple) 0);
}

.circle.per-75 {
    background-image: conic-gradient(var(--darkpurple) 75%, var(--lightpurple) 0);
}

.circle.per-76 {
    background-image: conic-gradient(var(--darkpurple) 76%, var(--lightpurple) 0);
}

.circle.per-77 {
    background-image: conic-gradient(var(--darkpurple) 77%, var(--lightpurple) 0);
}

.circle.per-78 {
    background-image: conic-gradient(var(--darkpurple) 78%, var(--lightpurple) 0);
}

.circle.per-79 {
    background-image: conic-gradient(var(--darkpurple) 79%, var(--lightpurple) 0);
}

.circle.per-80 {
    background-image: conic-gradient(var(--darkpurple) 80%, var(--lightpurple) 0);
}

.circle.per-81 {
    background-image: conic-gradient(var(--darkpurple) 81%, var(--lightpurple) 0);
}

.circle.per-82 {
    background-image: conic-gradient(var(--darkpurple) 82%, var(--lightpurple) 0);
}

.circle.per-83 {
    background-image: conic-gradient(var(--darkpurple) 83%, var(--lightpurple) 0);
}

.circle.per-84 {
    background-image: conic-gradient(var(--darkpurple) 84%, var(--lightpurple) 0);
}

.circle.per-85 {
    background-image: conic-gradient(var(--darkpurple) 85%, var(--lightpurple) 0);
}

.circle.per-86 {
    background-image: conic-gradient(var(--darkpurple) 86%, var(--lightpurple) 0);
}

.circle.per-87 {
    background-image: conic-gradient(var(--darkpurple) 87%, var(--lightpurple) 0);
}

.circle.per-88 {
    background-image: conic-gradient(var(--darkpurple) 88%, var(--lightpurple) 0);
}

.circle.per-89 {
    background-image: conic-gradient(var(--darkpurple) 89%, var(--lightpurple) 0);
}

.circle.per-90 {
    background-image: conic-gradient(var(--darkpurple) 90%, var(--lightpurple) 0);
}

.circle.per-91 {
    background-image: conic-gradient(var(--darkpurple) 91%, var(--lightpurple) 0);
}

.circle.per-92 {
    background-image: conic-gradient(var(--darkpurple) 92%, var(--lightpurple) 0);
}

.circle.per-93 {
    background-image: conic-gradient(var(--darkpurple) 93%, var(--lightpurple) 0);
}

.circle.per-94 {
    background-image: conic-gradient(var(--darkpurple) 94%, var(--lightpurple) 0);
}

.circle.per-95 {
    background-image: conic-gradient(var(--darkpurple) 95%, var(--lightpurple) 0);
}

.circle.per-96 {
    background-image: conic-gradient(var(--darkpurple) 96%, var(--lightpurple) 0);
}

.circle.per-97 {
    background-image: conic-gradient(var(--darkpurple) 97%, var(--lightpurple) 0);
}

.circle.per-98 {
    background-image: conic-gradient(var(--darkpurple) 98%, var(--lightpurple) 0);
}

.circle.per-99 {
    background-image: conic-gradient(var(--darkpurple) 99%, var(--lightpurple) 0);
}

.circle.per-100 {
    background-image: conic-gradient(var(--darkpurple) 100%, var(--lightpurple) 0);
}

.circle .inner {
    display: flex;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 115px;
    height: 115px;
    background: white;
    border-radius: 50%;
    font-size: 1.85em;
    font-weight: 300;
    color: black;
}

HTML:

<div class="column column-100">
  <div class="wrap-circles">
    <p>Operation in progress</p>
    <div class={circle(@progress_bar_value)}>
      <div class="inner"><%= @progress_bar_value %>%</div>
    </div>
  </div>
</div>

You can improve upon it using SASS for any other variant, framework.
This works from 0 to 100 (integer numbers).

Hope it is of use to someone.

3150512415

3150512415

You don’t need all those classes—you can set CSS custom properties from HTML:

background-image: conic-gradient(var(--darkpurple) var(--angle), var(--lightpurple) 0);

<div style="--angle:50%">

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews