A context is created once, outside any component, with createContext(defaultValue) — usually in its own module, so that the provider and every reader import the same object. What it returns holds no data; it is an identity, a key that a provider and a reader agree on.
A component provides a value by rendering the context itself as a component with a value prop, and reads the nearest provided value with useContext. With no provider above it, a reader gets the defaultValue, which is why null is the right default when there is no sensible fallback.
const ThemeContext = createContext('light'); // the fallback value
function Badge() {
const theme = useContext(ThemeContext); // reads the nearest provider above
return <b className={theme}>{theme}</b>;
}
export default function App() {
return <>
<Badge /> {/* no provider above: renders "light" */}
<ThemeContext value="dark"><Badge /></ThemeContext>
</>;
}Before React 19 7,897 the provider had to be written <ThemeContext.Provider value="dark">. That form still works and you will meet it in every older code base, but <ThemeContext> is now the documented syntax and .Provider is slated for removal. The matching <ThemeContext.Consumer> render-prop component is legacy too.
React walks up from the reader to find the closest provider, so a provider rendered by the same component that reads the context has no effect on that read. Nesting two providers of one context is legal: the inner shadows the outer for its own subtree, which is how a preview pane renders a light card inside a dark application.