Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Context / Provider Example

Live Demo

Overview

This example demonstrates vgui’s Context / Provider pattern — the SolidJS-equivalent of createContext / useContext / <Provider> for dependency injection down the element tree.

The example is split across three files — theme.rs (the context type, marker, and a leaf consumer), panel.rs (a sub-component that both consumes and provides), and main.rs (the root provider and entry point) — to show that a Context<T> marker crosses file/module boundaries for free.

It features:

  • Context::new() — a zero-sized, const-constructable typed marker stored in a plain static, keyed by TypeId.
  • <Provider context={...} value={...}> — pushes a value onto a thread-local stack before evaluating children and pops it after, so descendants observe the value during construction.
  • use_context / use_context_or — read the nearest ancestor provider value (or a default fallback).
  • Nearest-ancestor resolution: a nested <Provider> shadows an outer one for its subtree; after the inner provider closes, the outer value is visible again.

Source Code

theme.rs — the context type, marker, and leaf consumer:

#![allow(unused)]
fn main() {
use vgui::prelude::*;

/// A theme mode propagated through the element tree via `<Provider>`.
#[derive(Clone, PartialEq)]
pub enum Mode {
    Light,
    Dark,
}

/// The context marker. Zero-sized, stored in a plain `static`.
pub static MODE: Context<Mode> = Context::new();

/// A box that reads the nearest `MODE` provider, falling back to `Light`
/// when no provider is active. `css!` takes literal CSS, so the `if`/`else`
/// picks one of two literal blocks — no dynamic interpolation needed.
///
/// This leaf consumer defines no provider of its own; it reads whatever
/// ancestor provider was pushed in another module, proving a `Context<T>`
/// marker crosses file boundaries for free.
pub fn themed_box(label: &str) -> impl gpui::IntoElement {
    let mode = use_context_or(&MODE, || Mode::Light);
    let style = if matches!(mode, Mode::Dark) {
        css! {
            background: #1a1a2a;
            color: #ffffff;
            padding: 16px;
            margin: 8px;
            border-radius: 8px;
        }
    } else {
        css! {
            background: #f5f5f5;
            color: #111111;
            padding: 16px;
            margin: 8px;
            border-radius: 8px;
        }
    };
    view! {
        <div style={style}>{label.to_string()}</div>
    }
}
}

panel.rs — a sub-component in its own module that imports the marker from theme.rs, reads the ancestor provider, and nests its own override:

#![allow(unused)]
fn main() {
use crate::theme::{themed_box, Mode, MODE};
use vgui::prelude::*;

/// A sub-component living in its own module. It imports the `MODE` marker
/// from `theme.rs`, reads the ancestor provider set in `main.rs`, and nests
/// its own override provider — demonstrating that context resolution is
/// per-render, not per-module.
#[allow(non_snake_case)]
pub fn ThemePanel() -> impl gpui::IntoElement {
    view! {
        <div class="flex flex-col gap-2">
            {themed_box("panel: inherits root context")}
            <Provider context={MODE} value={Mode::Dark}>
                {themed_box("panel: overridden to dark")}
            </Provider>
        </div>
    }
}
}

main.rs — the root provider and dual entry point:

#![cfg_attr(target_family = "wasm", no_main)]

mod panel;
mod theme;

use gpui::{px, size, App, Bounds, WindowBounds, WindowOptions};
use panel::ThemePanel;
use theme::{themed_box, Mode, MODE};
use vgui::prelude::*;

#[cfg(not(target_family = "wasm"))]
use gpui_platform::application;

#[cfg(target_family = "wasm")]
use gpui_platform::single_threaded_web;

fn app() -> impl gpui::IntoElement {
    let (mode, set_mode) = create_signal(Mode::Light);
    view! {
        <Provider context={MODE} value={mode.get()}>
            <div class="flex flex-col p-4 gap-2 w-[400px] h-[400px]">
                {themed_box("root context (toggles)")}
                <ThemePanel />
                <button class="p-2 bg-[#0066cc] text-white rounded"
                    on:click={click(move |cx| set_mode.update(cx, |m|
                        *m = match *m { Mode::Light => Mode::Dark, Mode::Dark => Mode::Light }))}>
                    {"toggle root theme"}
                </button>
            </div>
        </Provider>
    }
}

fn run() {
    #[cfg(not(target_family = "wasm"))]
    let gpui_app = application();

    #[cfg(target_family = "wasm")]
    let gpui_app = single_threaded_web();

    let launch = |cx: &mut App| {
        let bounds = Bounds::centered(None, size(px(400.), px(400.0)), cx);
        cx.open_window(
            WindowOptions {
                window_bounds: Some(WindowBounds::Windowed(bounds)),
                ..Default::default()
            },
            |window, cx| vgui::mount(window, cx, app),
        )
        .unwrap();
    };

    #[cfg(not(target_family = "wasm"))]
    gpui_app.run(launch);

    #[cfg(target_family = "wasm")]
    std::mem::forget(gpui_app.run_embedded(launch));
}

#[cfg(not(target_family = "wasm"))]
fn main() {
    run();
}

#[cfg(target_family = "wasm")]
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn start() {
    gpui_platform::web_init();
    vgui::intercept_keyboard_events();
    run();
}

Key Concepts

Context::new() static

Context<T> is a zero-sized, const-constructable typed marker. It carries no value itself — it only identifies a context type, keyed by TypeId of T. Declare it in a plain static:

#![allow(unused)]
fn main() {
static MODE: Context<Mode> = Context::new();
}

One context per type. If you need two contexts of the same logical type, wrap the value in a newtype (struct Alt(Mode);) and declare a second Context<Alt>.

<Provider context={} value={}>

The <Provider> builtin pushes a value onto a thread-local stack before evaluating its children and pops it after. Descendants constructed between enter and exit observe the value via use_context. The context and value attributes are both required; any other attribute is rejected.

The type of value must match the context’s T__provider_scope_enter unifies T from both arguments, so a mismatch is a compile error.

use_context / use_context_or

use_context(&CTX) walks the provider stack top-down and returns the nearest matching entry, or None if no provider is active. use_context_or(&CTX, || default) falls back to the closure when no provider is present.

Nearest-ancestor resolution & nested override

The stack is searched top-down, so a nested <Provider> shadows an outer one within its subtree. In the example, the root provider binds MODE to a signal-driven Mode (toggled by the button), while an inner provider overrides it to Mode::Dark — so the “overridden to dark” box stays dark regardless of the toggle, while the “root context” box follows the signal.

Cross-module context

A Context<T> marker is a static keyed by TypeId on a thread-local provider stack. The stack is per-render, not per-module: a provider pushed in one file is visible to any descendant component constructed during that render, regardless of which module defines it. So a use crate::theme::MODE in panel.rs (or any other module) sees the nearest ancestor <Provider> that was entered in main.rs — no wiring, re-export, or parameter passing required. The three-file split in this example makes that flow visible in source: theme.rs owns the marker and a leaf consumer, panel.rs imports the marker and both consumes and provides, and main.rs pushes the root provider.

Programmatic provider (provide_context)

For advanced manual use and tests, provide_context(&CTX, value) returns a ProviderGuard that pops the stack on drop (RAII). The <Provider> macro builtin is the primary mechanism; provide_context is for cases where you need to provide a value outside a view! tree.

Running

Native:

cargo run -p vgui-context

Web (WASM):

cargo build --target wasm32-unknown-unknown -p vgui-context --release
wasm-bindgen --target web --out-dir examples/context/dist \
    --no-typescript target/wasm32-unknown-unknown/release/context.wasm
python3 scripts/serve_plain.py 8080 examples/context