TunkShif

TunkShif

Sprout UI - unstyled and accessible components for Phoenix Framework

Sprout UI provides a collection of common UI components. All of these components are completely unstyled, and they are built following the WAI-ARIA guide to be accessible.


Currently, it is not ready for a stable release yet, but I just can’t wait to share the progress I’ve made and get some feedback. Check out the links below for more information.

Website (including documentation and component demos) | Github Repo


Design goals for Sprout UI:

renderless function component

If you’ve never heard about this concept, it’s highly recommended to check out this informative blog post by @LostKobrakai . Also check out libraries like Downshift JS and Zag JS in front-end world.

A renderless function component does not render any visible HTML tags, but just provides necessary context information for rendering. And it is the user’s responsibility to decide what component structure should be rendered like.

For example, a simple dialog component is used like this:

<.dialog :let={api}>
  <button {api.trigger_attrs}>Open modal</button>

  <div {api.container_attrs}>
    <div {api.backdrop_attrs}></div>
    <div {api.panel_attrs}>
      <h2 {api.title_attrs}>Dialog</h2>
      <p {api.description_attrs}>...</p>
    </div>
  </div>
</dialog>

The component itself won’t render any HTML structures for your dialog, but just takes care of all the needed attributes for user interactions and accessibility.

no unnecessary round-trip to server for user interactions

Under the hood, Sprout UI components are using custom elements for handling user interactions like clicking or keyboard events. You won’t need to communicate to the server-side just for opening a modal or toggling an element.

And it can be used with LiveView or just plain server-side rendered templates withour LiveView.

styling and transition made easy

Generally, a Sprout UI component is composed of multiple component parts. For example, the dialog example above may eventually rendered like some HTML structures like this:

<sp-dialog data-state="open">
  <button data-part="trigger">Open modal</button>

  <div data-part="container">
    <div data-part="backdrop"></div>
    <div data-part="panel" aria-attributes>
      <h2 data-part="title">Dialog</h2>
      <p data-part="description">...</p>
    </div>
  </div>
<sp-dialog>

sp-dialog is a custom element for handling dialog opening/closing interactions and managing focus. Each component part is rendered with a data-part attribute. And for components with multiple UI states, like dialog can be open or closed, the state is presented in a data-state attribute as well. You can use CSS attribute selector to style a component part or a specific state.

[data-part="backdrop"] {
  background-color: #000;
  opacity: 0.6;
}

:where([data-state="open"]) [data-part="backdrop"] {
  /* ... */
}

Also, Sprout UI comes with a Tailwind CSS plugin that makes it easier for styling different UI states using variants. So you can just do things like ui-open:opacity-100 ui-closed:opacity-0.

And Sprout UI provides a transition utility to help you add transitions when a component part entering or leaving in a declarative way. Here is an example of how you can add transitions for a dialog backdrop:

  <div
    data-transition
    data-enter="transition duration-300 ease-in-out"
    data-enter-from="opacity-0"
    data-enter-to="opacity-100"
    data-leave="transition duration-300 ease-in-out"
    data-leave-from="opacity-100"
    data-leave-to="opacity-0"
    {api.backdrop_attrs}
  >
  </div>

Currently I’m still having lots of work to do: writing tests and docs, fixing some existing issues with these components, implementing more common component patterns.

So feel free to try it out yourself and share your ideas!

Most Liked

teamon

teamon

I took great inspiration from Sprout when deciding on how to build headless, thank you @TunkShif :purple_heart:

Where Next?

Popular in Announcing Top

martinthenth
Hello everybody :wave: Recently, some of my colleagues talked about database ids and uuids and their problems, and I remembered the pain...
New
mischov
import Meeseeks.CSS html = HTTPoison.get!("https://news.ycombinator.com/").body for story &lt;- Meeseeks.all(html, css("tr.athing")) do...
New
devonestes
Introducing assertions, the library that helps you write really great test assertions! GitHub: GitHub - devonestes/assertions: Helpful a...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
kip
Image is an image processing library for Elixir. It is based upon the fabulous vix library that provides a libvips wrapper for Elixir. I...
622 18474 194
New
type1fool
WebAuthnLiveComponent WebAuthnComponents See this post about renaming the package. Passwordless authentication for Phoenix LiveView app...
New
aditya7iyengar
Rummage.Ecto and Rummage.Phoenix provide ways to perform Searching, Sorting and Pagination over Ecto queries and Phoenix collections. Fo...
New
achempion
Hi, I would like to tell about my initiative to further maintain and develop Waffle project which is the fork of Arc library. The progre...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
trisolaran
Hi! :waving_hand: I would like to present LiveSelect, a little library that I wrote to easily add a dynamic selection input to your LV f...
198 10858 107
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement