diff --git a/docs/assets/data-quality-checks/expected-values/warning.png b/docs/assets/data-quality-checks/expected-values/warning.png
deleted file mode 100644
index 8948ab77e0..0000000000
Binary files a/docs/assets/data-quality-checks/expected-values/warning.png and /dev/null differ
diff --git a/docs/components/general-props/warning.md b/docs/components/general-props/warning.md
index 0af8a064c2..eb90f8d9f6 100644
--- a/docs/components/general-props/warning.md
+++ b/docs/components/general-props/warning.md
@@ -1,10 +1,6 @@
!!! info
- Visual warnings for spacing issues are available across all forms where values are entered.
- When a value includes leading or trailing spaces, the system automatically highlights it inside a warning-colored chip and displays a tooltip message indicating the issue in real time — for example:
+ The **List** input flags entries with leading or trailing whitespace. A clean value like `ship` renders as a normal chip; values like ` ship` or `ship ` get a yellow chip background so the extra space stands out at a glance. A warning icon also appears next to the field title. Hover the icon to see the full list of values with extra spaces.
- > “The following value has leading or trailing spaces: ‘ship ’”
-
-
-
-
\ No newline at end of file
+ The chip is still saved exactly as typed. Re-enter the value without the surrounding spaces if the engine is meant to match it literally, since the comparison is exact and does not trim.
+
diff --git a/docs/data-quality-checks/expected-values-check.md b/docs/data-quality-checks/expected-values-check.md
deleted file mode 100644
index 5eba1550ca..0000000000
--- a/docs/data-quality-checks/expected-values-check.md
+++ /dev/null
@@ -1,114 +0,0 @@
-# Expected Values
-
-### Definition
-
-*Asserts that values are contained within a list of expected values.*
-
-{%
- include-markdown "components/general-props/warning.md"
- start=''
- end=''
-%}
-
-### Field Scope
-
-**Single:** The rule evaluates a single specified field.
-
-**Accepted Types**
-
-| Type | Supported |
-|-------------|:------------------------:|
-| `Date` |
:material-check-circle:{ style="color: #4caf50" }
|
-| `Timestamp` | :material-check-circle:{ style="color: #4caf50" }
|
-| `Integral` | :material-check-circle:{ style="color: #4caf50" }
|
-| `Fractional`| :material-check-circle:{ style="color: #4caf50" }
|
-| `String` | :material-check-circle:{ style="color: #4caf50" }
|
-| `Boolean` | :material-check-circle:{ style="color: #4caf50" }
|
-
-### General Properties
-
-{%
- include-markdown "components/general-props/index.md"
- start=''
- end=''
-%}
-
-### Specific Properties
-
-Specify the list of expected values for the data in the field.
-
-| Name | Description |
-|------------|---------------------------------------------------------------|
-| List
| A predefined set of values against which the data is validated. |
-
-### Anomaly Types
-
-{%
- include-markdown "components/anomaly-support/index.md"
- start=''
- end=''
-%}
-
-### Example
-
-**Objective**: *Ensure that all O_ORDERSTATUS entries in the ORDERS table only contain expected order statuses: "O", "F", and "P".*
-
-**Sample Data**
-
-| O_ORDERKEY | O_ORDERSTATUS |
-|------------|----------------|
-| 1 | F |
-| 2 | O |
-| 3 | P |
-| 4 | X |
-
-=== "Payload example"
- ``` json
- {
- "description": "Ensure that all O_ORDERSTATUS entries in the ORDERS table only contain expected order statuses: "O", "F", and "P"",
- "coverage": 1,
- "properties": {
- "list":["O","F","P"]
- },
- "tags": [],
- "fields": ["O_ORDERSTATUS"],
- "additional_metadata": {"key 1": "value 1", "key 2": "value 2"},
- "rule": "expectedValues",
- "container_id": {container_id},
- "template_id": {template_id},
- "filter": "1=1"
- }
- ```
-
-**Anomaly Explanation**
-
-In the sample data above, the entry with `O_ORDERKEY` **4** does not satisfy the rule because the `O_ORDERSTATUS` "X" is not on the list of expected order statuses ("O", "F", "P").
-
-=== "Flowchart"
- ``` mermaid
- graph TD
- A[Start] --> B[Retrieve O_ORDERSTATUS]
- B --> C{Is O_ORDERSTATUS in 'O', 'F', 'P'?}
- C -->|Yes| D[Move to Next Record/End]
- C -->|No| E[Mark as Anomalous]
- E --> D
- ```
-
-=== "SQL"
- ```sql
- -- An illustrative SQL query demonstrating the rule applied to example dataset(s).
- select
- o_orderkey
- , o_orderstatus
- from orders
- where
- o_orderstatus not in ('O', 'F', 'P')
- ```
-
-**Potential Violation Messages**
-
-!!! example "Record Anomaly"
- The `O_ORDERSTATUS` value of `'X'` does not appear in the list of expected values
-
-!!! example "Shape Anomaly"
- In `O_ORDERSTATUS`, 25.000% of 4 filtered records (1) do not appear in the list of expected values
diff --git a/docs/data-quality-checks/expected-values/api.md b/docs/data-quality-checks/expected-values/api.md
new file mode 100644
index 0000000000..6f5bf370de
--- /dev/null
+++ b/docs/data-quality-checks/expected-values/api.md
@@ -0,0 +1,60 @@
+# :material-api:{ .middle style="color: var(--q-brick)" } Expected Values Check API
+
+The Expected Values check is created and managed through the standard Quality Checks API by setting `rule` to `expectedValues`, listing one field under `fields`, and providing the allowed vocabulary in `properties.list`.
+
+!!! tip
+ For complete API documentation, including request and response schemas, visit the [API docs](https://demo.qualytics.io/api/docs){:target="_blank"}.
+
+## Endpoints
+
+| Method | Path | Purpose |
+|:---|:---|:---|
+| `POST` | `/api/quality-checks` | Create a new Expected Values check. |
+| `GET` | `/api/quality-checks/{id}` | Retrieve an Expected Values check by ID. |
+| `PUT` | `/api/quality-checks/{id}` | Update an existing Expected Values check. |
+| `DELETE` | `/api/quality-checks/{id}` | Delete (or archive) an Expected Values check. |
+
+**Permission**: Requires **Author** team permission (or above) for `POST`, `PUT`, and `DELETE`; **Reporter** team permission (or above) for `GET`.
+
+## Payload Example
+
+Create an Expected Values check on `o_orderstatus` with `POST /api/quality-checks`, showing every field accepted by the create endpoint:
+
+```json
+{
+ "description": "Order status must be O, F, or P",
+ "rule": "expectedValues",
+ "fields": ["o_orderstatus"],
+ "container_id": 145,
+ "coverage": 1,
+ "filter": null,
+ "properties": {"list": ["O", "F", "P"]},
+ "tags": ["enum", "orders"],
+ "additional_metadata": {"jira": "DATA-1101"},
+ "anomaly_message_field": null,
+ "template_id": null,
+ "status": "Active",
+ "owner_id": 7,
+ "default_anomaly_assignee_id": 18
+}
+```
+
+## Field Notes
+
+| Field | Required | Notes |
+|:---|:---:|:---|
+| `description` | Yes | Free-text description shown in the UI. |
+| `rule` | Yes | Must be `"expectedValues"`. |
+| `fields` | Yes | Array with exactly one field name. Expected Values is a single-field rule. |
+| `container_id` | Yes | ID of the container (table or file) the check runs against. |
+| `coverage` | No | Fractional value between `0` and `1`. `1` (default) emits Record Anomalies for each failing row; `< 1` rolls failures into a single Shape Anomaly when the failing fraction exceeds the threshold. |
+| `filter` | No | Spark SQL `WHERE` expression. Applied **before** the value comparison, so only filtered rows are evaluated. Send `null` for no filter. |
+| `properties.list` | Yes | Array of accepted values. The list type is inferred from its contents: all-numeric → numeric list; all-boolean → boolean list; otherwise string list. Match the list type to the field type to avoid every row failing on type coercion. |
+| `properties.is_element_context` | No | Set to `true` to evaluate each element of an array field against the list (uses `array_forall`). Auto-enabled by the platform for `Array[String]` fields. Only string element lists are supported today. |
+| `tags` | No | List of tag names applied to the check for filtering and organization. |
+| `additional_metadata` | No | Free-form key-value pairs (typically links to catalog, tickets, governance records). |
+| `anomaly_message_field` | No | Name of a source-record field whose value should be used as the anomaly message instead of the system-generated one. In the UI this corresponds to the **Custom Anomaly Description** toggle. Applies to Record Anomalies (coverage = 1); ignored for Shape Anomalies. |
+| `template_id` | No | ID of a Check Template to associate the check with. `null` if not using a template. |
+| `status` | No | `"Active"` (default) or `"Draft"`. Draft checks are not evaluated by Scans. |
+| `owner_id` | No | ID of the user who owns the check. Defaults to the user creating the check when omitted. |
+| `default_anomaly_assignee_id` | No | ID of the user automatically assigned to anomalies produced by the check. |
diff --git a/docs/data-quality-checks/expected-values/examples.md b/docs/data-quality-checks/expected-values/examples.md
new file mode 100644
index 0000000000..539afd9a6b
--- /dev/null
+++ b/docs/data-quality-checks/expected-values/examples.md
@@ -0,0 +1,252 @@
+# Expected Values Check Examples
+
+Three real-world scenarios that show how the Expected Values check is typically used in production: enforcing a status enum on a transactional table, enforcing a country code on a customer table with a filter, and validating an `Array[String]` field element-wise.
+
+The **Sample Data** tables follow the platform's source-records view: one row per source record, with **only the offending cell** highlighted (similar to how the application marks an anomalous field).
+
+=== "Status Enum on Orders"
+
+ **The situation:** Your `orders` table holds one row per order. The `o_orderstatus` column must be one of three values: `O` (open), `F` (filled), or `P` (pending). Any other value is invalid and should fire a Record Anomaly so the responsible team can fix it.
+
+ **Check configuration**
+
+ | Field | Value |
+ |:---|:---|
+ | Rule | Expected Values |
+ | Fields | `o_orderstatus` |
+ | List | `O`, `F`, `P` |
+ | Filter | *(none)* |
+ | Coverage | 100% |
+ | Custom Anomaly Description | Off |
+ | Status | Active |
+ | Owner | *(check creator)* |
+ | Anomaly Assignee | *(orders-domain owner)* |
+ | Tags | `enum`, `orders` |
+ | Additional Metadata | `jira: DATA-1101` |
+ | Description | Order status must be O, F, or P |
+
+ **Payload**
+
+ ```json
+ {
+ "description": "Order status must be O, F, or P",
+ "rule": "expectedValues",
+ "fields": ["o_orderstatus"],
+ "container_id": 145,
+ "coverage": 1,
+ "filter": null,
+ "properties": {"list": ["O", "F", "P"]},
+ "tags": ["enum", "orders"],
+ "additional_metadata": {"jira": "DATA-1101"},
+ "anomaly_message_field": null,
+ "template_id": null,
+ "status": "Active",
+ "owner_id": 7,
+ "default_anomaly_assignee_id": 18
+ }
+ ```
+
+ **Sample Data**
+
+ | o_orderkey | o_orderstatus | o_totalprice |
+ |:---|:---|:---|
+ | 1 | F | 173665.47 |
+ | 2 | O | 46929.18 |
+ | 3 | P | 193846.25 |
+ | 4 | X | 32151.78 |
+
+ **What gets flagged**
+
+ Row 4's `o_orderstatus = X` is not in the list of expected values. The platform raises one Record Anomaly tied to the `o_orderstatus` field on that row. The other columns (`o_orderkey`, `o_totalprice`) are not part of the violation and are not highlighted.
+
+ !!! example "Record Anomaly"
+ The field `o_orderstatus` has value `'X'`, which is not in the list of expected values
+
+ **Flowchart**
+
+ ```mermaid
+ graph TD
+ A["No filter, evaluate all rows"] --> B{"Is o_orderstatus NULL?"}
+ B -->|Yes| D["Row passes"]
+ B -->|No| C{"Is o_orderstatus
in (O, F, P)?"}
+ C -->|Yes| D
+ C -->|No| E["Emit Record Anomaly on o_orderstatus"]
+ ```
+
+ **Equivalent SQL**
+
+ ```sql
+ -- Rows the Expected Values check would flag on the orders table.
+ SELECT o_orderkey, o_orderstatus, o_totalprice
+ FROM orders
+ WHERE o_orderstatus IS NOT NULL
+ AND o_orderstatus NOT IN ('O', 'F', 'P');
+ ```
+
+=== "Country Code with Filter"
+
+ **The situation:** Your `customers` table is global, but the compliance team only requires the country code vocabulary to be enforced on North-American customers (`region = 'NA'`). The accepted codes for North America are `US`, `CA`, and `MX`. Customers in other regions should be ignored by this check. They are validated by region-specific checks elsewhere.
+
+ **Check configuration**
+
+ | Field | Value |
+ |:---|:---|
+ | Rule | Expected Values |
+ | Fields | `country_code` |
+ | List | `US`, `CA`, `MX` |
+ | Filter | `region = 'NA'` |
+ | Coverage | 100% |
+ | Custom Anomaly Description | Off |
+ | Status | Active |
+ | Owner | *(check creator)* |
+ | Anomaly Assignee | *(compliance lead)* |
+ | Tags | `compliance`, `geo` |
+ | Additional Metadata | `jira: DATA-1212` |
+ | Description | North-American country codes must be US, CA, or MX |
+
+ **Payload**
+
+ ```json
+ {
+ "description": "North-American country codes must be US, CA, or MX",
+ "rule": "expectedValues",
+ "fields": ["country_code"],
+ "container_id": 212,
+ "coverage": 1,
+ "filter": "region = 'NA'",
+ "properties": {"list": ["US", "CA", "MX"]},
+ "tags": ["compliance", "geo"],
+ "additional_metadata": {"jira": "DATA-1212"},
+ "anomaly_message_field": null,
+ "template_id": null,
+ "status": "Active",
+ "owner_id": 7,
+ "default_anomaly_assignee_id": 24
+ }
+ ```
+
+ **Sample Data** (filtered to `region = 'NA'`)
+
+ | customer_id | region | country_code |
+ |:---|:---|:---|
+ | 9001 | NA | US |
+ | 9002 | NA | CA |
+ | 9003 | NA | BR |
+ | 9004 | NA | MX |
+
+ !!! note
+ The customer with `region = 'EU'` is not shown here because the filter `region = 'NA'` excludes it before evaluation. Cross-region values are intentionally outside the scope of this check.
+
+ **What gets flagged**
+
+ Row 3's `country_code = BR` is not in the North-American vocabulary. One Record Anomaly is emitted on the `country_code` field of that row. The `region` column is part of the filter (not the rule) and is not highlighted.
+
+ !!! example "Record Anomaly"
+ The field `country_code` has value `'BR'`, which is not in the list of expected values [filter: region = 'NA']
+
+ **Flowchart**
+
+ ```mermaid
+ graph TD
+ A["Apply filter: region = 'NA'"] --> B{"Is country_code NULL?"}
+ B -->|Yes| D["Row passes"]
+ B -->|No| C{"Is country_code
in (US, CA, MX)?"}
+ C -->|Yes| D
+ C -->|No| E["Emit Record Anomaly on country_code"]
+ ```
+
+ **Equivalent SQL**
+
+ ```sql
+ -- Rows the Expected Values check would flag on the customers table.
+ SELECT customer_id, region, country_code
+ FROM customers
+ WHERE region = 'NA'
+ AND country_code IS NOT NULL
+ AND country_code NOT IN ('US', 'CA', 'MX');
+ ```
+
+=== "Element-Wise on an Array Field"
+
+ **The situation:** Your `articles` table has a `tags` column of type `Array[String]`. Every tag must come from the editorial vocabulary `news`, `sports`, `tech`, `finance`. A single out-of-vocabulary element in an array invalidates the row, even if the other elements are valid.
+
+ The platform auto-enables element-wise evaluation for `Array[String]` fields, so you do not need to set `properties.is_element_context` manually.
+
+ **Check configuration**
+
+ | Field | Value |
+ |:---|:---|
+ | Rule | Expected Values |
+ | Fields | `tags` |
+ | List | `news`, `sports`, `tech`, `finance` |
+ | Filter | *(none)* |
+ | Coverage | 100% |
+ | Custom Anomaly Description | Off |
+ | Status | Active |
+ | Owner | *(check creator)* |
+ | Anomaly Assignee | *(content-platform owner)* |
+ | Tags | `vocabulary`, `editorial` |
+ | Additional Metadata | `jira: DATA-1330` |
+ | Description | Article tags must come from the editorial vocabulary |
+
+ **Payload**
+
+ ```json
+ {
+ "description": "Article tags must come from the editorial vocabulary",
+ "rule": "expectedValues",
+ "fields": ["tags"],
+ "container_id": 318,
+ "coverage": 1,
+ "filter": null,
+ "properties": {
+ "list": ["news", "sports", "tech", "finance"],
+ "is_element_context": true
+ },
+ "tags": ["vocabulary", "editorial"],
+ "additional_metadata": {"jira": "DATA-1330"},
+ "anomaly_message_field": null,
+ "template_id": null,
+ "status": "Active",
+ "owner_id": 7,
+ "default_anomaly_assignee_id": 31
+ }
+ ```
+
+ **Sample Data**
+
+ | article_id | title | tags |
+ |:---|:---|:---|
+ | a-001 | Markets close higher | `["finance", "news"]` |
+ | a-002 | Game recap | `["sports"]` |
+ | a-003 | New gadget review | ["tech", "gossip"] |
+ | a-004 | Daily roundup | `[]` |
+
+ **What gets flagged**
+
+ Row 3's `tags` array contains `gossip`, which is not in the editorial vocabulary. The whole `tags` cell is marked because the violation is at the field level (the array as a whole failed `array_forall`). Rows with an empty or NULL array pass.
+
+ !!! example "Record Anomaly"
+ The field `tags` has value `'["tech", "gossip"]'`, which is not in the list of expected values
+
+ **Flowchart**
+
+ ```mermaid
+ graph TD
+ A["No filter, evaluate all rows"] --> B{"Is tags NULL or empty?"}
+ B -->|Yes| D["Row passes"]
+ B -->|No| C{"Is every element of tags
in (news, sports, tech, finance)?"}
+ C -->|Yes| D
+ C -->|No| E["Emit Record Anomaly on tags"]
+ ```
+
+ **Equivalent SQL**
+
+ ```sql
+ -- Rows the Expected Values check would flag on the articles table.
+ SELECT article_id, title, tags
+ FROM articles
+ WHERE tags IS NOT NULL
+ AND size(tags) > 0
+ AND NOT array_forall(tags, x -> x IN ('news', 'sports', 'tech', 'finance'));
+ ```
diff --git a/docs/data-quality-checks/expected-values/faq.md b/docs/data-quality-checks/expected-values/faq.md
new file mode 100644
index 0000000000..545d1607cf
--- /dev/null
+++ b/docs/data-quality-checks/expected-values/faq.md
@@ -0,0 +1,84 @@
+# :material-help-circle-outline:{ .middle style="color: var(--q-brick)" } Expected Values Check FAQ
+
+Common questions about how the Expected Values check evaluates values, how it handles NULLs and types, and how anomalies are reported.
+
+## Behavior
+
+### How are NULLs treated?
+
+NULL values **pass** the check. The engine evaluates `column IS NULL OR column IN (list)`, so a row with a NULL in the selected field is never reported as a violation. If missing values are also a problem, pair Expected Values with a [Not Null](../not-null-check.md){:target="_blank"} check on the same field.
+
+### Is matching case-sensitive?
+
+Yes. `"O"` does not match `"o"`. The check uses Spark's `IN` operator, which compares strings exactly. If you need case-insensitive matching, normalize the data upstream or use a [Satisfies Expression](../satisfies-expression-check.md){:target="_blank"} check with `lower(field) IN ('o', 'f', 'p')`.
+
+### Is whitespace trimmed?
+
+No. `" O "` does not match `"O"`. The Authored Check modal shows a warning when an entry has leading or trailing whitespace, but it does not trim the entry for you. Clean the list before saving the check, or normalize the column upstream.
+
+### Does the filter clause run before or after the comparison?
+
+Before. The platform applies the filter first and then evaluates the value comparison only on the rows that pass the filter. Rows outside the filter cannot trigger an Expected Values anomaly.
+
+### What happens when the list type does not match the field type?
+
+Spark cannot coerce strings to numbers or booleans through `IN`, so every non-NULL row in the column will fail the check. For example, an Expected Values check on an integer column with a string list `["1", "2", "3"]` flags every row. Always match the list type to the field type: numeric lists for numeric fields, boolean lists for boolean fields, string lists for string/date/timestamp fields.
+
+## Array Fields
+
+### Can Expected Values evaluate every element of an array?
+
+Yes, for `Array[String]` fields. The platform auto-enables element-wise evaluation when the target field is an `Array[String]`, equivalent to `array_forall(field, element -> element IN (list))`. Every element must be in the list; a single out-of-vocabulary element invalidates the row.
+
+Element-wise evaluation is supported only for string element lists today. For other array element types, the check falls back to scalar comparison and is not currently recommended.
+
+### Do empty or NULL arrays pass?
+
+Yes. An empty array (`[]`) has no elements to evaluate and passes by definition. A NULL array also passes (the standard NULL handling rule applies). If you want to flag empty arrays as a violation, pair Expected Values with a separate check (such as a Satisfies Expression on `size(field) > 0`).
+
+## Anomaly Reporting
+
+### Does Expected Values produce Record or Shape Anomalies?
+
+Both, controlled by coverage:
+
+- **Coverage = 1 (100%, default):** every failing row emits a **Record Anomaly** with the offending value in the message.
+- **Coverage < 1:** failures are rolled up into a single **Shape Anomaly** for the dataset, fired only when the failing fraction exceeds the threshold.
+
+### What does the Record Anomaly message look like?
+
+```
+The field '' has value '', which is not in the list of expected values
+```
+
+When a filter is set, the message is followed by `[filter: ]`.
+
+### What does the Shape Anomaly message look like?
+
+```
+For the field '', X.XXX% of N records (K) have values not in the list of expected values
+```
+
+- **X.XXX%:** the fraction of evaluated rows that failed the comparison.
+- **N:** the number of rows evaluated (after the filter, if any).
+- **K:** the number of rows whose value was non-NULL and not in the list.
+
+When a filter is set, the message is followed by `[filter: ]`.
+
+### How are source records highlighted in the app?
+
+The platform highlights **only the offending cell** in the field the check applies to. Other columns in the same row are rendered normally. This matches the rule's semantics: the violation is about a single field's value, not the entire row. The Sample Data tables in [Examples](examples.md){:target="_blank"} mirror this convention.
+
+## Configuration
+
+### Can I paste a list of values from a spreadsheet?
+
+Yes. The List field in the Authored Check modal accepts pasted values; the platform splits the pasted text on line breaks, trims leading and trailing whitespace from each line, and drops empty lines. This is convenient for importing a vocabulary maintained in a spreadsheet column.
+
+### Can I use Expected Values together with a Check Template?
+
+Yes. Set `template_id` to the ID of an existing Check Template. The template's properties (including the list) are reused by the check, and any future updates to the template propagate to every check linked to it.
+
+### Does Custom Anomaly Description work for Expected Values?
+
+Yes, for Record Anomalies. Setting `anomaly_message_field` to the name of a source-record field uses that field's value as the anomaly message instead of the system-generated one. The field is silently ignored for Shape Anomalies (coverage < 1), because Shape messages use a fixed template.
diff --git a/docs/data-quality-checks/expected-values/how-it-works.md b/docs/data-quality-checks/expected-values/how-it-works.md
new file mode 100644
index 0000000000..ddd5b29acf
--- /dev/null
+++ b/docs/data-quality-checks/expected-values/how-it-works.md
@@ -0,0 +1,108 @@
+# How Expected Values Checks Work
+
+This page covers everything the Expected Values check does, in detail: how it decides which rows are violations, how it handles NULLs and types, what the filter clause does, how coverage controls whether you get Record or Shape anomalies, and how the check behaves on Array fields.
+
+If you only need a quick reference, the [Introduction](introduction.md){:target="_blank"} covers the formal definition, field scope, and general/anomaly properties.
+
+## How the Check Evaluates Values
+
+Every Expected Values check follows the same evaluation flow:
+
+1. **Apply the filter clause.** If the check has a `filter` set, only the rows that match the filter expression continue to the next step. Rows outside the filter are ignored and cannot cause a violation.
+2. **Skip NULL values.** Rows where the selected field is NULL pass the check automatically and are never reported as violations.
+3. **Compare each non-NULL value to the list.** A row passes when the value is **exactly** equal to one of the list entries (same type, same case, same whitespace). A row fails when the value is non-NULL and not in the list.
+4. **Decide Record or Shape anomaly based on coverage.** At 100% coverage every failing row becomes a Record Anomaly; below 100% coverage, the violation is rolled up into a single Shape Anomaly when the failing fraction exceeds the configured threshold (see [Coverage and Tolerance](#coverage-and-tolerance)).
+
+The order of operations matters: the filter is applied **before** the value comparison, so rows that the filter excludes cannot contribute to the violation count.
+
+## NULL Handling
+
+NULL values **pass** the Expected Values check. The engine evaluates `column IS NULL OR column IN (list)`, so a NULL is treated as compliant regardless of what is in the list. This is intentional: Expected Values asserts membership in a vocabulary, not presence.
+
+If missing values are also a violation, pair Expected Values with a [Not Null](../not-null-check.md){:target="_blank"} check on the same field. Together they enforce "this field must be present **and** must match the vocabulary."
+
+## Type Coercion and Matching Rules
+
+The list is interpreted according to its contents and the column type:
+
+- **All entries numeric** → the list is treated as a numeric list. Integers and decimals are compared numerically. `100` matches `100.0`.
+- **All entries boolean** (`true`, `false`) → the list is treated as a boolean list. The column must be of `Boolean` type.
+- **Anything else (including mixed or empty)** → the list is treated as strings and the column value is compared as a string.
+
+A few practical consequences worth knowing:
+
+- **Case-sensitive.** `"O"` does **not** match `"o"`. If you need case-insensitive matching, normalize the source data upstream or use a [Satisfies Expression](../satisfies-expression-check.md){:target="_blank"} check with `lower(field) IN (...)`.
+- **Whitespace-sensitive.** `" O "` does **not** match `"O"`. The Authored Check modal warns when an entry has leading or trailing whitespace, but it does not trim it for you.
+- **Type mismatches fail.** If the column is integer and the list is `["O", "F", "P"]` (strings), Spark cannot coerce `"O"` to an integer; every non-NULL row will fail the check. Keep the list's type aligned with the field's type.
+- **Date and Timestamp.** There is no native date list type; values are compared as strings against the column's rendered form. Prefer the ISO-8601 representation that matches how your engine serializes the column (`YYYY-MM-DD` for `Date`, `YYYY-MM-DD HH:MM:SS` for `Timestamp`).
+
+## Array Fields
+
+When the check targets an `Array[String]` field, the platform automatically switches to element-wise evaluation:
+
+- The check evaluates `array_forall(field, element -> element IN (list))`.
+- **Every element** of the array must be in the list. A row fails as soon as one element is outside the vocabulary, even if the other elements pass.
+- An empty array passes (no elements to evaluate). A NULL array also passes (NULL handling rules above).
+
+Element-wise evaluation is supported **only** for string element lists today. Array fields with numeric or boolean lists fall through to scalar comparison and are not currently recommended. See [Complex Data Types](../../container/complex-data-types.md){:target="_blank"} for the broader picture of how rule types interact with arrays.
+
+## The Filter Clause
+
+The filter clause is a Spark SQL `WHERE` expression applied **before** the value comparison. Use it to:
+
+1. **Scope the check.** Restrict the vocabulary requirement to a subset of the data (for example, `region = 'NA'` to enforce a North-American country code vocabulary only on orders from that region).
+2. **Exclude legacy or pending rows.** A filter such as `created_at >= '2024-01-01'` keeps the check from flagging values that were valid under an earlier vocabulary.
+
+The filter is part of the check definition, so the anomaly message includes the filter expression (`[filter: ]`) when one is set, making it explicit which slice of data was evaluated when the anomaly fired.
+
+## Coverage and Tolerance
+
+Coverage is a fractional value between `0` and `1` that controls whether failing rows produce **Record** anomalies (one per row) or a single **Shape** anomaly (the dataset-level rollup):
+
+- **`1.0` (100%, default):** every non-NULL row must match the list. Each failing row produces a **Record Anomaly**.
+- **`< 1.0`:** the check tolerates a fraction of records failing the comparison. When the actual failing fraction exceeds the threshold, a single **Shape Anomaly** fires for the dataset. No Record Anomalies are produced in this mode.
+
+Lower coverage values are useful when a small known fraction of out-of-vocabulary values is expected (a slow migration, a deprecation window, a legacy import). Use it with care: lowering coverage by 0.5% means a regression introducing failures in up to 0.5% of rows will look identical to the tolerated baseline and won't fire.
+
+## The Resulting Anomalies
+
+### Record Anomaly
+
+Emitted at 100% coverage, one per failing row.
+
+```
+The field '' has value '', which is not in the list of expected values
+```
+
+### Shape Anomaly
+
+Emitted below 100% coverage, one per dataset, when the failing fraction exceeds the tolerance threshold.
+
+```
+For the field '', X.XXX% of N records (K) have values not in the list of expected values
+```
+
+- **X.XXX%:** the fraction of evaluated rows that failed the comparison.
+- **N:** the number of rows evaluated (after the filter, if any).
+- **K:** the number of rows whose value was non-NULL and not in the list.
+
+When a filter is set, both messages are followed by `[filter: ]`.
+
+## Source Records and How Anomalies Render
+
+When you open the source records for an Expected Values anomaly, the platform highlights **only the offending cell** for the field the check applies to. Other columns in the same row are rendered normally. This matches how the check semantics work: the violation is about a single field's value, not the entire row.
+
+The Sample Data tables on the [Examples](examples.md){:target="_blank"} page follow the same convention: one row per source record, with only the failing cell visually marked.
+
+## Relationship with Other Rule Types
+
+The Expected Values check is most powerful when used together with a small set of complementary rule types:
+
+| Rule Type | Why pair it with Expected Values |
+|:---|:---|
+| [Not Null](../not-null-check.md){:target="_blank"} | Expected Values lets NULLs pass. Pair with Not Null on the same field when missing values are also a violation. |
+| [Required Values](../required-values-check.md){:target="_blank"} | Expected Values asserts that values come **from** a list (subset). Required Values asserts that the field contains **at least** the values in a list (superset). They are mirror images: use both when the column must contain every required value *and* nothing outside the vocabulary. |
+| [Matches Pattern](../matches-pattern-check.md){:target="_blank"} | Use Matches Pattern when the accepted values follow a regular structure rather than a finite list (for example, `SKU-\d{4}`). Expected Values is the right tool when the vocabulary is small and enumerable. |
+| [Satisfies Expression](../satisfies-expression-check.md){:target="_blank"} | Use a custom Spark SQL expression when you need case-insensitive matching (`lower(field) IN ('o', 'f', 'p')`) or any other normalization Expected Values does not perform natively. |
+
+The Expected Values check can also be created automatically as an **AI Managed check** when a Profile operation detects that a column has a small, stable set of distinct values. See the [AI Managed Checks Introduction](../ai-managed/introduction.md){:target="_blank"} for how AI Managed checks are generated and how editing one converts it to Authored.
diff --git a/docs/data-quality-checks/expected-values/introduction.md b/docs/data-quality-checks/expected-values/introduction.md
new file mode 100644
index 0000000000..bdf13a0592
--- /dev/null
+++ b/docs/data-quality-checks/expected-values/introduction.md
@@ -0,0 +1,101 @@
+# Expected Values Check
+
+## Definition
+
+*Asserts that the value in the selected field is contained in a predefined list of accepted values.*
+
+## Overview
+
+The Expected Values rule enforces a closed vocabulary on a single column. The check compares each row's value against a list configured on the rule and flags every row whose value is not in the list. Typical use cases include:
+
+- Enforcing status enums (`order_status`, `payment_state`).
+- Restricting a country, currency, or locale code to an allowed set.
+- Validating short categorical fields (`tier`, `channel`, `segment`).
+- Asserting that an array column contains only elements from a known vocabulary.
+
+NULL values pass the check. The rule only evaluates non-NULL values against the list, so an Expected Values check on a nullable field never fires on missing values. Pair it with a [Not Null](../not-null-check.md){:target="_blank"} check on the same field when missingness is also a violation.
+
+## Field Scope
+
+**Single:** The rule evaluates one field per check.
+
+**Accepted Types**
+
+| Type | Supported |
+|-------------|:------------------------:|
+| `Date` | :material-check-circle:{ style="color: #4caf50" }
|
+| `Timestamp` | :material-check-circle:{ style="color: #4caf50" }
|
+| `Integral` | :material-check-circle:{ style="color: #4caf50" }
|
+| `Fractional`| :material-check-circle:{ style="color: #4caf50" }
|
+| `String` | :material-check-circle:{ style="color: #4caf50" }
|
+| `Boolean` | :material-check-circle:{ style="color: #4caf50" }
|
+
+For `Date` and `Timestamp`, the list is compared as strings against the column's rendered value. See [How It Works](how-it-works.md){:target="_blank"} for details and recommended formats.
+
+## General Properties
+
+{%
+ include-markdown "components/general-props/index.md"
+ start=''
+ end=''
+%}
+
+## Specific Properties
+
+Specify the predefined list of values that the field is allowed to contain.
+
+| Name | Description |
+|:---|:---|
+| List
| The predefined set of accepted values. Every non-NULL value in the selected field must match one of these entries exactly (case-sensitive, no whitespace trimming). |
+
+{%
+ include-markdown "components/general-props/warning.md"
+ start=''
+ end=''
+%}
+
+## Anomaly Types
+
+{%
+ include-markdown "components/anomaly-support/index.md"
+ start=''
+ end=''
+%}
+
+## Next Steps
+
+
+
+- **How It Works**
+
+ ---
+
+ Full semantics on NULL handling, type coercion, array support, filter and coverage behavior, and resulting anomalies.
+
+ [:octicons-arrow-right-24: How It Works](how-it-works.md)
+
+- **Examples**
+
+ ---
+
+ Three production scenarios with sample data, anomaly messages, and the equivalent SQL the check evaluates.
+
+ [:octicons-arrow-right-24: Examples](examples.md)
+
+- **API**
+
+ ---
+
+ Payload shape and field notes for creating an Expected Values check programmatically.
+
+ [:octicons-arrow-right-24: API](api.md)
+
+- **FAQ**
+
+ ---
+
+ Short answers to questions about case-sensitivity, NULL handling, array support, and anomaly reporting.
+
+ [:octicons-arrow-right-24: FAQ](faq.md)
+
+
diff --git a/docs/data-quality-checks/overview-of-a-check.md b/docs/data-quality-checks/overview-of-a-check.md
index faf8627146..f55e45dae2 100644
--- a/docs/data-quality-checks/overview-of-a-check.md
+++ b/docs/data-quality-checks/overview-of-a-check.md
@@ -173,7 +173,7 @@ For more details about check rule types, please refer to the [**Rule Types Overv
| [Equal To Field](../data-quality-checks/equal-to-field-check.md) | Asserts that this field is equal to another field. |
| [Exists in](../data-quality-checks/exists-in-check.md) | Asserts if the rows of a compared table/field of a specific Datastore exists in the selected table/field.|
| [Expected Schema](../data-quality-checks/expected-schema-check.md) | Asserts that all selected fields are present and that all declared data types match expectations. |
-| [Expected Values](../data-quality-checks/expected-values-check.md) | Asserts that values are contained within a list of expected values. |
+| [Expected Values](../data-quality-checks/expected-values/introduction.md) | Asserts that values are contained within a list of expected values. |
| [Field Count](../data-quality-checks/field-count-check.md) | Asserts that there must be exactly a specified number of fields. |
| [Freshness Check](../data-quality-checks/freshness-check.md) | Asserts that data was added or updated in the data asset after a declared time. |
| [Greater Than](../data-quality-checks/greater-than-check.md) | Asserts that the field is a number greater than (or equal to) a value. |
diff --git a/docs/data-quality-checks/rule-types-overview.md b/docs/data-quality-checks/rule-types-overview.md
index 7f08905007..6777384888 100644
--- a/docs/data-quality-checks/rule-types-overview.md
+++ b/docs/data-quality-checks/rule-types-overview.md
@@ -24,7 +24,7 @@ Here’s an overview of the rule types and their purposes:
| [Equal To Field](../data-quality-checks/equal-to-field-check.md) | Asserts that this field is equal to another field. |
| [Exists in](../data-quality-checks/exists-in-check.md) | Asserts if the rows of a compared table/field of a specific Datastore exists in the selected table/field.|
| [Expected Schema](../data-quality-checks/expected-schema-check.md) | Asserts that all selected fields are present and that all declared data types match expectations. |
-| [Expected Values](../data-quality-checks/expected-values-check.md) | Asserts that values are contained within a list of expected values. |
+| [Expected Values](../data-quality-checks/expected-values/introduction.md) | Asserts that values are contained within a list of expected values. |
| [Field Count](../data-quality-checks/field-count-check.md) | Asserts that there must be exactly a specified number of fields. |
| [Greater Than](../data-quality-checks/greater-than-check.md) | Asserts that the field is a number greater than (or equal to) a value. |
| [Greater Than Field](../data-quality-checks/greater-than-field-check.md) | Asserts that this field is greater than another field. |
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
index acec27b991..fd9dbbf20d 100644
--- a/docs/stylesheets/extra.css
+++ b/docs/stylesheets/extra.css
@@ -241,6 +241,7 @@
--q-negative: #E91E63;
--q-warning: #ffbb33;
--q-orange: #F96719;
+ --q-orange-rgb: 249, 103, 25;
--q-invalid: #5A678F;
/* MkDocs Variables */
@@ -558,6 +559,7 @@
--q-negative: #E91E63;
--q-warning: #ffbb33;
--q-orange: #F96719;
+ --q-orange-rgb: 249, 103, 25;
--q-invalid: #828db0;
/* MkDocs Variables */
@@ -955,6 +957,19 @@
color: var(--q-brick);
}
+/* Inspired by the source-records anomalous-cell treatment in the Qualytics app:
+ orange outline plus a warning-tinted background on cells whose value failed a check.
+ Use inside markdown table cells to mark a single value as anomalous. */
+.anomalous-cell {
+ display: inline-block;
+ padding: 0.05rem 0.4rem;
+ border: 1px solid var(--q-orange);
+ border-radius: 4px;
+ background-color: rgba(var(--q-orange-rgb), 0.12);
+ color: var(--q-brick);
+ font-weight: 500;
+}
+
.text-sm {
font-size: 0.7rem;
}
diff --git a/mkdocs.yml b/mkdocs.yml
index 649473ca96..bd34c5446a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -319,7 +319,12 @@ nav:
- Equal to Field: data-quality-checks/equal-to-field-check.md
- Exists In: data-quality-checks/exists-in-check.md
- Expected Schema: data-quality-checks/expected-schema-check.md
- - Expected Values: data-quality-checks/expected-values-check.md
+ - Expected Values:
+ - Introduction: data-quality-checks/expected-values/introduction.md
+ - How It Works: data-quality-checks/expected-values/how-it-works.md
+ - Examples: data-quality-checks/expected-values/examples.md
+ - API: data-quality-checks/expected-values/api.md
+ - FAQ: data-quality-checks/expected-values/faq.md
- Field Count: data-quality-checks/field-count-check.md
- Freshness Checks: data-quality-checks/freshness-check.md
- Greater Than: data-quality-checks/greater-than-check.md
@@ -1167,7 +1172,8 @@ plugins:
'checks/equal-to-field-check.md': 'data-quality-checks/equal-to-field-check.md'
'checks/exists-in-check.md': 'data-quality-checks/exists-in-check.md'
'checks/expected-schema-check.md': 'data-quality-checks/expected-schema-check.md'
- 'checks/expected-values-check.md': 'data-quality-checks/expected-values-check.md'
+ 'checks/expected-values-check.md': 'data-quality-checks/expected-values/introduction.md'
+ 'data-quality-checks/expected-values-check.md': 'data-quality-checks/expected-values/introduction.md'
'checks/field-count-check.md': 'data-quality-checks/field-count-check.md'
'checks/freshness-check.md': 'data-quality-checks/freshness-check.md'
'checks/greater-than-check.md': 'data-quality-checks/greater-than-check.md'