Oct 30, 2025
5 min read
GUI,
Rust,

Modern Rust GUI Development Framework: Similarities between GPUI and TailwindCSS

If you are familiar with TailwindCSS, you will definitely feel a sense of déjà vu with GPUI’s API. This is because GPUI’s API for styling is directly inspired by TailwindCSS.

Yes, GPUI has adopted an atomic design approach for UI styling:

    div()
        .flex()
        .size_full()
        .p_4()
        .bg(rgb(0xf0f0f0))
        .text_color(black())
        .child(nav())

ElementId

ElementId is the unique identifier for GPUI elements. It is used to reference elements in the GPUI component tree. Therefore, it’s important to maintain the uniqueness of IDs, similar to DOM element IDs in front-end development. With unique IDs, we can easily attach events to specific elements. After adding an ID to an element, its type changes from E to Stateful<E>.

div().id("my-element").child("Hello, World!")

Render vs RenderOnce

All elements either implement RenderOnce or Render. However, RenderOnce is generally used to define components, while Render is used to define views (objects that can be drawn to the screen). RenderOnce consumes ownership of self, while Render operates on a mutable reference to self.

// Render - View persists and renders multiple times
impl Render for MyView {
    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        // Can access and modify state of self
        div().child(Label::new("Hello"))
    }
}

// RenderOnce - One-time component
impl RenderOnce for MyComponent {
    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
        // Consume self, typically used to build reusable component patterns
        div().child(Label::new("Component content"))
    }
}

Main differences:

  • Render - Types that implement Render are usually entities with persistent state that call the render method on each frame and can access and modify the component’s internal state.
  • RenderOnce - Usually used to build UI fragments with no state or simple state by consuming self to create one-time UI elements.

In GPUI, you can simply think of RenderOnce as defining components and Render as defining views.

Context

As seen above, the render method signature in Render includes a context parameter cx: &mut Context<Self>. This is an entity context instance used to handle context-level operations. These contexts are passed as references, allowing interaction with global state, windows, entities, and system services.

UI

Overall, if you’re familiar with TailwindCSS, GPUI’s API won’t feel foreign. However, there are some styles and effects that GPUI may not have corresponding APIs for.

Animations and Transitions

GPUI only supports simple animations, which can be implemented using .with_animation(id, animation, animator). Currently, no API related to transition effects has been found. Of course, theoretically, we could implement transition effects ourselves, but the implementation cost would be high, meaning we’d need to manage state ourselves and have more granular control over components. If a component requires state management, simply implementing RenderOnce won’t meet requirements - we must implement Render.

In the official examples, rotation animation effects are applied to SVG elements because SVG elements implement the with_transformation method. The reason is that at the underlying API level, only the paint_svg method provides parameters for transition effects:

pub fn paint_svg(
        &mut self,
        bounds: Bounds<Pixels>,
        path: SharedString,
        mut data: Option<&[u8]>,
        transformation: TransformationMatrix, // provides parameters for transition effects
        color: Hsla,
        cx: &App,
    ) -> Result<()>

Ordinary components are not drawn via the paint_svg method, so some common animation effects may require us to implement them from the bottom components up.

Parent-child component style inheritance

In frontend development, most frameworks or styling libraries support adding styles to parent components, with child components inheriting the parent’s styles. This is very efficient and useful when creating effects. However, in GPUI, I haven’t yet found a way to control this yet. A simple example is when the background of a button component should change color when the mouse hovers over it, and the child element span’s color should also change accordingly. In front-end frameworks, especially TailwindCSS, we could do this:

<button class="bg-blue-500 hover:bg-blue-700 text-white hover:text-slate-300 ">
 <span> primary </span>
 </button>

Here, the span inherits the text style from the button. However, in GPUI, I haven’t found an API to control child elements within hover events yet.

Others

Of course, GPUI only implements some of the common styling APIs from TailwindCSS; other less common effects will have to be implemented by yourself.

For example, a common frontend component like a circular ⭕️ progress bar is difficult to implement in GPUI using only top-level APIs.