# Storage

Keep a value between visits, follow changes, and read cookies on the server.

Use the storage helpers to save a value and read it back later. They support localStorage, sessionStorage, and cookies, and let you listen for changes.

For a user preference, such as a theme or sidebar state, pass a storage to [`createPreference`](/docs/core/behaviors/preferences#remember-the-choice).

## Choose a Storage

Choose by how long the value should last and whether the server needs to read it:

| Storage                | Lasts                         | Shared With             | Server Reads It |
| ---------------------- | ----------------------------- | ----------------------- | --------------- |
| `createLocalStorage`   | Until removed                 | Other tabs on the site  | No              |
| `createSessionStorage` | While the tab is open         | This tab                | No              |
| `createCookieStorage`  | One year, or `maxAge` seconds | Requests and other tabs | Yes             |

A note you want to keep between visits can use localStorage. A temporary draft can use sessionStorage. A preference that changes server-rendered content can use a cookie.

## Keep a Value

These helpers store strings under a name, which becomes the localStorage or sessionStorage key, or the cookie's name:

```ts
import { createLocalStorage } from "@ui-sdk/core"

const note = createLocalStorage("note")

note.set("Buy milk")
note.get()
note.remove()
note.get()
```

The first `get()` returns `"Buy milk"`. After `remove()`, it returns `null`.

Type a note and reload. Switch storage types, or open another tab, to see which values carry over.

## When Storage Is Unavailable

A browser can block storage or run out of space. If a read fails, `get()` returns `null`. For localStorage and sessionStorage, `set` and `remove` return `true` when the write succeeds, or `false` when it fails.

Use `available()` for an initial check. A later write can still fail, so check its return value too:

```ts
if (!note.available()) {
  console.log("Saving is unavailable here.")
}

if (!note.set("Buy milk")) {
  console.log("The note wasn't saved.")
}
```

A full storage may still let you read or remove an existing value.

For cookies, `true` means no error was reported. It doesn't confirm that the cookie was saved or removed.

On the server, localStorage and sessionStorage are unavailable. To read a saved value there, [use a cookie](#read-a-cookie-on-the-server).

## Follow Changes

Read the starting value with `get`, then subscribe to changes. `subscribe` doesn't call your listener with the initial value:

```ts
function logNote(value: string | null) {
  console.log(value ?? "")
}

logNote(note.get())
const stop = note.subscribe(logNote)
```

Writes through a storage with the same kind and ID notify listeners on the page, including writes through `note` itself. LocalStorage also follows changes in other tabs. Cookies follow other tabs in browsers that support `cookieStore`.

Call the returned function when you no longer need the subscription, such as when the component unmounts:

```ts
stop()
```

## Read a Cookie on the Server

Pass the request's `Cookie` header to read a saved value during server rendering:

```ts
import { createCookieStorage } from "@ui-sdk/core"

function readDensity(request: Request) {
  const density = createCookieStorage("density", {
    cookie: request.headers.get("cookie"),
  })

  return density.get()
}
```

Create the storage inside your request handler, as above, so each visitor's cookie stays separate. In the browser, it reads `document.cookie`.

On the server, `available()` returns `true` when you supply a `Cookie` header, but `set` and `remove` return `false`. The helper reads the request's cookies; it doesn't set response headers. Its `readsOnServer` flag lets helpers such as `createPreference` use the saved value in their initial state.

By default, cookies last one year, use path `/` and `SameSite=Lax`, and use `Secure` on HTTPS pages. You can change these with `maxAge`, `path`, `domain`, `sameSite`, and `secure`.

## Show a Value Before Hydration

The server can't read localStorage or sessionStorage. For styles that depend on a saved value, `script` generates JavaScript that puts it on a `data-*` attribute before hydration:

```ts
import { createLocalStorage } from "@ui-sdk/core"

const densityStorage = createLocalStorage("density")
const script = densityStorage.script({
  attribute: "data-density",
  values: ["comfortable", "compact"],
})
```

Render the element with its default attribute, such as `data-density="comfortable"`. Render `script` as inline JavaScript immediately after that element. It reads the saved value and updates the preceding element's attribute while the page loads.

Only values listed in `values` are written. Missing or invalid saved values leave the attribute as the server rendered it. Attribute names must start with `data-`.

The script updates the attribute. Read the storage when the component mounts and subscribe to keep its state current. When you [render the script in your framework](/docs/server-rendering#render-the-storage-script-yourself), account for the attribute changing before hydration.

Scripts with the same storage kind and ID share one initial read. A cookie storage's `script` returns `undefined`, because the server can render the cookie's value itself.

For user preferences, [apply them with `Preferences`](/docs/core/behaviors/preferences#apply-preferences) to render one script and keep their attributes up to date.

## API Reference

- `id` (`string`): The ID the value is kept under: the localStorage or sessionStorage key, or the cookie's name. Example: [Keep a Value](#keep-a-value).
- `kind` (`Kind`): Where the value is kept: `"local"`, `"session"` or `"cookie"`. Example: [Choose a Storage](#choose-a-storage).
- `readsOnServer` (`boolean`): Whether the server reads the value too, so it renders right from the first render. True for cookies. Example: [Read a Cookie on the Server](#read-a-cookie-on-the-server).
- `script` (`(options: StorageScriptOptions) => string`): The inline script a component renders right after its root, which puts the saved value on the root before hydration, so CSS shows it on the first paint. Undefined for a cookie, since the server renders its value. Example: [Show a Value Before Hydration](#show-a-value-before-hydration).
- `available` (`() => boolean`): Whether storage appears available. A later write can still fail. On the server, a cookie with a request header is available for reading only. Example: [When Storage Is Unavailable](#when-storage-is-unavailable).
- `get` (`() => string | null`): The value kept under the ID, or null when nothing is, or the storage is unavailable. Example: [Keep a Value](#keep-a-value).
- `set` (`(value: string) => boolean`): Saves a value under the ID. Returns false when writing is unavailable or the browser reports a write failure. For cookies, true means the assignment didn't throw; it doesn't confirm the cookie was saved. Example: [Keep a Value](#keep-a-value).
- `remove` (`() => boolean`): Removes the value. Returns false when writing is unavailable or the browser reports a write failure. For cookies, true means the assignment didn't throw; it doesn't confirm the cookie was removed. Example: [Keep a Value](#keep-a-value).
- `subscribe` (`(listener: (value: string | null) => void) => () => void`): Hears the value change, through another storage with the same ID on the page or in another tab. Returns the function that stops it. Example: [Follow Changes](#follow-changes).

- `cookie?` (`string | null`): The request's `Cookie` header, so the server reads the same value the browser will. Example: [Read a Cookie on the Server](#read-a-cookie-on-the-server).
- `maxAge?` (`number`): How long the cookie lasts, in seconds. Example: [Read a Cookie on the Server](#read-a-cookie-on-the-server).
- `path?` (`string`): The paths that send the cookie. Example: [Read a Cookie on the Server](#read-a-cookie-on-the-server).
- `domain?` (`string`): The domain that sends the cookie. Defaults to the page's host. Example: [Read a Cookie on the Server](#read-a-cookie-on-the-server).
- `sameSite?` (`"lax" | "none" | "strict"`): Whether other sites' requests send the cookie. Example: [Read a Cookie on the Server](#read-a-cookie-on-the-server).
- `secure?` (`boolean`): Whether only HTTPS requests send the cookie. Defaults to true on HTTPS pages. Example: [Read a Cookie on the Server](#read-a-cookie-on-the-server).

- `attribute` (``data-${string}``): The attribute that shows the value, which has to start with `data-`, like `"data-color-scheme"`. Example: [Show a Value Before Hydration](#show-a-value-before-hydration).
- `values` (`readonly string[]`): The values it may show. Anything else saved under the ID leaves the root as the server rendered it. Example: [Show a Value Before Hydration](#show-a-value-before-hydration).
