Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Roblox/roact/llms.txt

Use this file to discover all available pages before exploring further.

As a component tree grows, passing the same value — a theme, a service reference, a locale setting — through every level of props becomes tedious and brittle. Context is Roact’s solution: it lets a component publish a value once, and any descendant can subscribe to it directly, no matter how deep in the tree it sits. Roact’s Context API is modelled on React’s Context API and is the recommended approach for dependency injection, dynamic theming, and scoped state storage.
Context was added in Roact 1.3.0. Make sure you are on at least that version before using Roact.createContext.

Creating a Context

Call Roact.createContext(defaultValue) to produce a context object. The defaultValue is returned to any Consumer that has no matching Provider ancestor — it acts as a safe fallback and makes components using the context testable in isolation.
Roact.createContext(defaultValue) -> { Provider, Consumer }
local ThemeContext = Roact.createContext({
    foreground = Color3.new(0, 0, 0),
    background = Color3.new(1, 1, 1),
})
createContext returns a table with exactly two fields: Provider and Consumer. Both are Roact components.

The Consumer Component

ThemeContext.Consumer accepts a single render prop: a function that receives the current context value and returns an element. Roact calls this function whenever the context value changes, so consuming components always have the latest value without any manual wiring.
local function ThemedButton(props)
    return Roact.createElement(ThemeContext.Consumer, {
        render = function(theme)
            return Roact.createElement("TextButton", {
                Size             = UDim2.new(0, 100, 0, 100),
                Text             = "Click Me!",
                TextColor3       = theme.foreground,
                BackgroundColor3 = theme.background,
            })
        end,
    })
end
If no Provider ancestor exists for ThemeContext, the defaultValue supplied to createContext is passed to render instead.

The Provider Component

ThemeContext.Provider accepts a value prop and any number of children via Roact.Children. Every Consumer that is a descendant of this Provider receives the value. When value changes (i.e. when a parent component calls setState with a new theme), all connected Consumer components automatically re-render with the updated value.
local ThemeController = Roact.Component:extend("ThemeController")

function ThemeController:init()
    self:setState({
        theme = {
            foreground = Color3.new(1, 1, 1),
            background = Color3.new(0, 0, 0),
        },
    })
end

function ThemeController:render()
    return Roact.createElement(ThemeContext.Provider, {
        value = self.state.theme,
    }, self.props[Roact.Children])
end
ThemeController wraps its children in the Provider and passes its current state.theme as the context value. Any ThemedButton rendered anywhere inside ThemeController automatically reflects the active theme. Changing self.state.theme (via setState) causes Roact to propagate the new value to all Consumer descendants.

Full Theming Example

Putting it all together:
-- 1. Define the context with a default (light) theme.
local ThemeContext = Roact.createContext({
    foreground = Color3.new(0, 0, 0),
    background = Color3.new(1, 1, 1),
})

-- 2. A leaf component that consumes the theme.
local function ThemedButton(props)
    return Roact.createElement(ThemeContext.Consumer, {
        render = function(theme)
            return Roact.createElement("TextButton", {
                Size             = UDim2.new(0, 100, 0, 100),
                Text             = "Click Me!",
                TextColor3       = theme.foreground,
                BackgroundColor3 = theme.background,
            })
        end,
    })
end

-- 3. A controller component that owns the active theme and provides it.
local ThemeController = Roact.Component:extend("ThemeController")

function ThemeController:init()
    self:setState({
        theme = {
            foreground = Color3.new(1, 1, 1),
            background = Color3.new(0, 0, 0),
        },
    })
end

function ThemeController:render()
    return Roact.createElement(ThemeContext.Provider, {
        value = self.state.theme,
    }, self.props[Roact.Children])
end
To use them together, mount ThemeController near the root of your tree and place ThemedButton anywhere inside it:
Roact.mount(
    Roact.createElement(ThemeController, {}, {
        -- ThemedButton will receive the dark theme from ThemeController.
        Button = Roact.createElement(ThemedButton),
    }),
    playerGui
)

Legacy Context (Deprecated)

Legacy context is a deprecated feature and will be removed in a future release of Roact. New code should always use Roact.createContext. The information below is provided only for maintaining older codebases.
Before Roact.createContext existed, Roact exposed an internal self._context table that components could read and write directly. Unlike the modern API, legacy context values do not update dynamically on their own — you must build your own update mechanism (typically a wrapper component with setState). Provider side — write to self._context in init:
local Provider = Roact.Component:extend("FooProvider")

-- A unique non-string key is recommended to avoid key collisions.
local FooKey = {}

function Provider:init()
    self._context[FooKey] = {
        value = 5,
    }
end
Consumer side — read from self._context in init:
local Consumer = Roact.Component:extend("FooConsumer")

function Consumer:init()
    self.foo = self._context[FooKey]
end

function Consumer:render()
    return Roact.createElement("TextLabel", {
        Text = "Foo: " .. self.foo.value,
    })
end
Prefer the createContext API for all new work. It supports dynamic updates, is easier to test, and will continue to be supported as Roact evolves.

Build docs developers (and LLMs) love