import { pageMetadata } from "@/lib/page-metadata" export const metadata = pageMetadata("docs/visibility") # Visibility Conditionally show or hide components based on state values and logic. ## State-Based Visibility Show/hide based on state values. Use `$state` with a JSON Pointer path: ```json { "type": "Alert", "props": { "message": "Form has errors" }, "visible": { "$state": "/form/hasErrors" } } ``` Visible when `/form/hasErrors` is truthy. ### Negation Use `not: true` to invert a condition: ```json { "type": "WelcomeBanner", "visible": { "$state": "/user/hasSeenWelcome", "not": true } } ``` Visible when `/user/hasSeenWelcome` is falsy. ## Auth-Based Visibility Show/hide based on authentication state. Expose your auth state in the state model (e.g. at `/auth/isSignedIn`): ```json { "type": "AdminPanel", "visible": { "$state": "/auth/isSignedIn" } } ``` For signed-out only: ```json { "type": "LoginPrompt", "visible": { "$state": "/auth/isSignedIn", "not": true } } ``` ## Comparison Operators Compare a state value to a literal or another state path. Use **one operator per condition** -- if multiple are provided, only the first one is evaluated (precedence: `eq` > `neq` > `gt` > `gte` > `lt` > `lte`). Add `"not": true` to invert the result of any condition. ```json // Equal { "visible": { "$state": "/user/role", "eq": "admin" } } // Not equal { "visible": { "$state": "/tab", "neq": "home" } } // Greater than { "visible": { "$state": "/cart/total", "gt": 100 } } // Greater than or equal { "visible": { "$state": "/cart/itemCount", "gte": 1 } } // Less than { "visible": { "$state": "/cart/total", "lt": 1000 } } // Less than or equal { "visible": { "$state": "/cart/itemCount", "lte": 10 } } ``` Comparison values can be literals or state references: ```json { "visible": { "$state": "/user/balance", "gte": { "$state": "/order/minimum" } } } ``` ## Combining Conditions (AND) Place multiple conditions in an array for implicit AND: ```json { "type": "SubmitButton", "visible": [ { "$state": "/form/isValid" }, { "$state": "/form/hasChanges" } ] } ``` All conditions must be true for the element to be visible. ## OR Conditions Use `$or` when at least one condition should be true: ```json { "type": "SpecialOffer", "visible": { "$or": [ { "$state": "/user/isVIP" }, { "$state": "/cart/total", "gt": 200 } ]} } ``` Visible when the user is VIP **or** the cart total exceeds 200. `$or` can contain any visibility conditions, including nested arrays (AND) and comparisons. ## Explicit AND Use `$and` when you need to nest AND logic inside `$or`: ```json { "type": "PromoCard", "visible": { "$or": [ { "$and": [ { "$state": "/user/isVIP" }, { "$state": "/cart/total", "gt": 50 } ]}, { "$state": "/promo/active" } ]} } ``` For top-level AND, the implicit array form is simpler: `[condition, condition]`. Use `$and` only when nesting inside `$or`. ## Always / Never Use boolean literals for constant visibility: ```json { "type": "Footer", "visible": true } ``` ```json { "type": "DeprecatedPanel", "visible": false } ``` ## Repeat-Scoped Conditions Inside a [repeat](/docs/data-binding#repeat), use `$item` and `$index` conditions to show/hide based on the current item: ### `$item` — Condition on item field ```json { "type": "Badge", "props": { "label": "Overdue" }, "visible": { "$item": "isOverdue" } } ``` With comparison: ```json { "type": "DiscountTag", "visible": { "$item": "price", "gt": 100 } } ``` ### `$index` — Condition on array index ```json { "type": "Divider", "visible": { "$index": true, "gt": 0 } } ``` This shows the divider for every item except the first (index 0). ### Filtered lists — `$item` on the repeat container Putting an `$item` condition directly on the element that declares `repeat` filters which items render. This is the natural way to build kanban columns, tabbed lists, or status sections from one state array: ```json { "type": "Stack", "repeat": { "statePath": "/tasks", "key": "id" }, "visible": { "$item": "status", "eq": "todo" }, "children": ["task-card"] } ``` One child renders per matching item; non-matching items are skipped. When the condition is an `$and` (or array) that mixes scopes, the `$state` parts gate the container itself (a false gate hides the whole shell) while the `$item`/`$index` parts filter items. A mixed `$or` cannot be split and is applied entirely per item. Filtered lists are currently implemented by the React renderer; other renderers evaluate the container condition outside the repeat scope. `$item` and `$index` conditions support the same comparison operators as `$state` (`eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `not`). ## Complex Example ```json { "type": "RefundButton", "props": { "label": "Process Refund" }, "visible": [ { "$state": "/auth/isSignedIn" }, { "$state": "/user/role", "eq": "support" }, { "$state": "/order/amount", "gt": 0 }, { "$state": "/order/isRefunded", "not": true } ] } ``` ## Quick Reference
| Condition | Syntax |
|---|---|
| Truthiness | {'{ "$state": "/path" }'} |
| Falsy (not) | {'{ "$state": "/path", "not": true }'} |
| Equal | {'{ "$state": "/path", "eq": value }'} |
| Not equal | {'{ "$state": "/path", "neq": value }'} |
| Greater than | {'{ "$state": "/path", "gt": number }'} |
| Greater or equal | {'{ "$state": "/path", "gte": number }'} |
| Less than | {'{ "$state": "/path", "lt": number }'} |
| Less or equal | {'{ "$state": "/path", "lte": number }'} |
| Item field (repeat) | {'{ "$item": "field" }'} |
| Item comparison | {'{ "$item": "field", "eq": value }'} |
| Index (repeat) | {'{ "$index": true, "gt": 0 }'} |
| AND (implicit) | {"[ condition, condition ]"} |
| AND (explicit) | {'{ "$and": [ condition, condition ] }'} |
| OR | {'{ "$or": [ condition, condition ] }'} |
| Always | {"true"} |
| Never | {"false"} |