Privacy-First React Components: Storage and Safe Defaults
A component’s privacy depends on what it reads, saves and sends. Visual simplicity tells you very little about any of those things.
Updated

Make persistence a deliberate choice
Consider a notes editor. The text field needs a value and a change handler. It does not automatically need an analytics client, a cloud account or permission to save every keystroke.
Keep the editing interface separate from the persistence layer. Let the application decide whether a change stays in memory, is saved on the device, or is submitted to a server. That makes the behavior easier to explain, test and replace.
For component work such as ObsidianKit, this is a useful design question: can someone use the interface without also accepting a hidden data-storage policy?
// A controlled editor leaves storage to its parent.
function NoteEditor({ value, onChange }) {
return (
<textarea
aria-label="Note"
value={value}
onChange={(event) => onChange(event.target.value)}
/>
)
}Local storage is not a secret vault
localStorage persists strings for an origin across browsing sessions. It is useful for low-risk preferences such as a chosen theme. Access can fail when browser policy blocks persistence, and private-browsing data is cleared when the private session ends.
Treat persistence as optional. If it fails, the interface should still work for the current session. Do not tell someone their work is saved when a storage operation failed.
Plan for scripts that can read the page
JavaScript running in your origin can access localStorage. A cross-site scripting vulnerability can expose its contents. Storing sensitive information there does not become safe simply because you did not create a database server.
Do not store session identifiers in localStorage. Review authentication separately, and avoid putting secrets into client-side code. Third-party scripts deserve the same scrutiny as your own scripts because they execute within the page.
Treat stored values as untrusted input when you read them back. Validate their shape and allowed values before using them. A previously saved value is not proof that the value is safe.
Handle a failed preference save honestly
Here is a small browser-storage boundary for a non-sensitive theme preference. The boolean result lets the caller distinguish “changed for this session” from “saved for next time.” Call it from the preference interaction, not during rendering.
The server-side guard makes the helper safe to import in a rendered React application. Runtime validation also prevents unexpected values from being persisted. This example is intentionally limited to preferences; it is not a design for storing private documents.
function saveTheme(theme: "light" | "dark"): boolean {
if (typeof window === "undefined") return false
if (theme !== "light" && theme !== "dark") return false
try {
window.localStorage.setItem("theme", theme)
return true
} catch {
return false
}
}Review the whole interaction
A privacy review should follow a real action from input to storage to network. A page that works offline may still make requests when a connection is available. List those requests and explain why they exist.
Use the following checklist before describing a component as privacy-first. It is more useful than counting dependencies or assuming a minimalist interface must collect less data.
- Can the user see whether their content stays on the device or leaves it?
- Do optional analytics, embeds and remote assets have a clear purpose?
- Can saved content be deleted, and can important work be exported?
- Does blocked storage produce an understandable state rather than silent data loss?
- Is user text rendered as text? If rich HTML is required, is a maintained sanitizer used before insertion?
- Have loading, saving, failure and empty states been reviewed with real examples?

