# `Mob.SizeClass`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/size_class.ex#L1)

The window's size class, `{horizontal, vertical}`, each `:compact` or
`:regular`.

Every screen socket carries it as `assigns.size_class`. The framework sets it
before `mount/3` and keeps it current; a screen never asks for it.

Size classes are the layout signal, not orientation or raw dimensions. An
iPad in Split View is a phone-shaped window on a tablet; a foldable flips
class when it opens. Typical values:

| Window                                    | Size class              |
|-------------------------------------------|-------------------------|
| iPhone, portrait                          | `{:compact, :regular}`  |
| Large iPhone (Plus, Pro Max, XR/11-class, 414pt wide or more), landscape | `{:regular, :compact}` |
| Smaller iPhones, landscape                | `{:compact, :compact}`  |
| iPad full screen, either orientation      | `{:regular, :regular}`  |
| iPad Slide Over, narrow Split View        | `{:compact, :regular}`  |
| Android phone, portrait                   | `{:compact, :regular}`  |
| Android phone, landscape                  | `{:regular, :compact}`  |
| Android tablet / unfolded foldable        | `{:regular, :regular}`  |

## Where the value comes from

  * **iOS** — the window's `traitCollection.horizontalSizeClass` and
    `.verticalSizeClass`, exactly as UIKit reports them.
  * **Android** — Android has no OS size class, so it is derived from the
    activity's `Configuration.screenWidthDp` / `screenHeightDp` with the
    Material window-size-class breakpoints: horizontal is `:compact` below
    600dp wide, vertical is `:compact` below 480dp tall; Material's
    "medium" and "expanded" both map to `:regular`. These are the numbers
    Android's own `w600dp` / `h480dp` resource qualifiers select on. See
    `decisions/2026-10-01-size-class-in-socket-assigns.md` for why this and
    not Compose's `WindowSizeClass`.

## Changes

When the window's class changes — rotation, an iPad Split View / Slide Over /
Stage Manager resize, an Android multi-window resize, a foldable opening —
every live screen gets the new value in `assigns.size_class` and then
`handle_info({:mob_size_class_changed, new}, socket)`. By the time that
callback runs the assign already holds `new`; a screen that only reads
`assigns.size_class` in `render/1` needs no clause at all, and one without a
matching clause is not crashed by it.

## Before the platform can answer

On iOS the BEAM can start before a window exists (a prewarmed or background
launch). Screens mounted then hold `placeholder/0`; the root view reports the
real value as soon as it appears, which arrives as an ordinary change.
`Mob.ScreenCase.mount_screen/4` uses the same placeholder unless told
otherwise.

# `class`

```elixir
@type class() :: :compact | :regular
```

One axis of a size class.

# `t`

```elixir
@type t() :: {class(), class()}
```

`{horizontal, vertical}`.

# `placeholder`

```elixir
@spec placeholder() :: t()
```

The value a screen holds when the platform cannot answer yet, and the
default under `Mob.ScreenCase`: `{:compact, :regular}`, a portrait phone.

# `valid?`

```elixir
@spec valid?(term()) :: boolean()
```

True for a well-formed size class.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
