mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 09:26:52 +01:00
Regenerate microsoft/knowledge from upstream BCApps instructions
The previous LLM-generated knowledge files contained factual
hallucinations. The most visible was the claim that `FindFirst` /
`FindLast` "forces a full-table scan" on an unfiltered record - it does
not; those APIs return a single row via the current key.
Other inaccuracies the audit found and fixed:
* `FindSet(true)` was described as "taking a LockTable". The correct
upstream phrasing is that `FindSet(true)` sets
`ReadIsolation::UpdLock` on the read. UpdLock and LockTable are
related but distinct mechanisms.
* The list of production-scale tables had been invented beyond the
upstream source (e.g. "Detailed Cust. Ledg. Entry") without a
citation. The regenerated list matches the ten tables upstream lists
with their P95 row counts.
* `SetLoadFields` guidance had been augmented with an extra mechanism
claim ("the database resolves the filter using the index without
hydrating the value") not present in upstream.
Approach: full regeneration of `microsoft/knowledge/` from the six
upstream BCApps Code Review instruction files, with Microsoft Learn /
the AL language reference as a secondary source. Every claim in every
regenerated file is anchored to a verbatim upstream quote (or a Learn
URL); the audit trail lives in artifacts/trace-<domain>.json on the
session workspace.
The PR #11 transaction/error-handling cluster is preserved verbatim:
* performance/understand-implicit-transaction-boundary.md
* performance/codeunit-run-as-atomic-sub-operation.{md,good.al,bad.al}
* performance/codeunit-run-requires-prior-commit-inside-transaction.{md,good.al,bad.al}
* performance/use-tryfunction-for-error-catching-not-rollback.{md,good.al,bad.al}
* performance/avoid-commit-inside-loops.{md,good.al,bad.al}
* security/commitbehavior-attribute-scopes-explicit-commits.{md,good.al,bad.al}
* testing/transactionmodel-attribute-governs-test-transactions.{md,good.al,bad.al}
These articles already cite Microsoft Learn and were carefully
cross-referenced; the regeneration skips their topics rather than
duplicating them.
File counts after regeneration:
performance 35 .md (5 preserved + 30 new)
privacy 17 .md
security 18 .md (1 preserved + 17 new)
style 33 .md
testing 1 .md (preserved)
ui 19 .md
upgrade 18 .md
Total 141 atomic knowledge files, each strictly one rule. All pass
.github/scripts/validate_frontmatter.py with 0 errors and 0 warnings.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
613c4b4019
commit
a9f3c50863
562 changed files with 6293 additions and 4869 deletions
|
|
@ -1,26 +0,0 @@
|
|||
page 51005 "UI Sample ActionTooltip Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Sales Header";
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
action(Post)
|
||||
{
|
||||
Caption = 'Post';
|
||||
ApplicationArea = All;
|
||||
// Declarative, not imperative. No period.
|
||||
ToolTip = 'This will post the invoice';
|
||||
}
|
||||
action(SendForApproval)
|
||||
{
|
||||
Caption = 'Send for approval';
|
||||
ApplicationArea = All;
|
||||
// Fragment that repeats the caption and says nothing new.
|
||||
ToolTip = 'Send for approval';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
page 51004 "UI Sample ActionTooltip Good"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Sales Header";
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
action(Post)
|
||||
{
|
||||
Caption = 'Post';
|
||||
ApplicationArea = All;
|
||||
// Imperative verb-first sentence, Sentence case, terminating period.
|
||||
ToolTip = 'Post the current sales invoice and finalize the transaction.';
|
||||
}
|
||||
action(SendForApproval)
|
||||
{
|
||||
Caption = 'Send for approval';
|
||||
ApplicationArea = All;
|
||||
ToolTip = 'Send the document to the approval workflow.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [tooltip, action, imperative, voice, period]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Action tooltips are imperative, verb-first sentences ending with a period
|
||||
|
||||
## Description
|
||||
|
||||
Action tooltips describe what the user will cause by invoking the action. The house style is an imperative verb-first sentence — `Post the current sales invoice and finalize the transaction.` — not a declarative one ("This will post …") and not a fragment ("Post invoice"). The imperative voice matches how the user reads the action bar: each tooltip completes the sentence "If I click this, the system will …" in the same grammatical form. Shortcut-key hints, when present, belong at the end of the tooltip and are retained verbatim.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Start the tooltip with the verb. Use Sentence case, end with a period, stay within the ~250-character budget. Keep one sentence unless the action genuinely needs two; avoid editorializing ("Easily post …") or narrating ("This action posts …"). Preserve any existing shortcut annotation.
|
||||
|
||||
See sample: `action-tooltips-are-imperative-and-end-with-period.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`ToolTip = 'This will post the invoice'` — declarative rather than imperative, no period. `ToolTip = 'Post'` — one-word fragment that duplicates the Caption and says nothing new. Both fail the scan-the-action-bar comprehension test.
|
||||
|
||||
See sample: `action-tooltips-are-imperative-and-end-with-period.bad.al`.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [terminology, disabled, invalid, whitelist, blacklist, voice]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Avoid banned UI terms; prefer the inclusive and direct replacements
|
||||
|
||||
## Description
|
||||
|
||||
Business Central's UI voice guidelines exclude four terms that carry connotations the product does not want to push onto users: "Disabled" (clinical/negative), "Invalid" (pejorative), "Whitelist" and "Blacklist" (terms with racial associations the industry has moved away from). The replacements read naturally, match the product's warm-and-direct voice, and align with Microsoft's cross-product terminology. The concern applies to user-visible text — captions, tooltips, error messages, notifications — not to variable names or code comments.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Replace "Disabled" with "Turned off" or "Not available". Replace "Invalid" with "Not valid" or "Incorrect". Replace "Whitelist" with "Allow list". Replace "Blacklist" with "Block list". Apply the substitution in all UI text surfaces: Caption, ToolTip, AboutTitle, AboutText, Label values, Message/Confirm/Error strings.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`ErrorLbl: Label 'Invalid input.'`, `Caption = 'Disabled Users'`, `ToolTip = 'Specifies the blacklist of blocked senders.'` — all three terms in places the user will read. The fix is literal substitution with the approved alternative.
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
page 51001 "UI Sample Caption Bad"
|
||||
{
|
||||
PageType = List;
|
||||
SourceTable = Customer;
|
||||
|
||||
// Noun phrase in Sentence case. Every other list page in the product is Title Case.
|
||||
Caption = 'Sales orders';
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
// Sentence phrase in Title Case. Reads as a typo.
|
||||
action(PostAndPrint)
|
||||
{
|
||||
Caption = 'Post And Print';
|
||||
ApplicationArea = All;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
page 51000 "UI Sample Caption Good"
|
||||
{
|
||||
PageType = List;
|
||||
SourceTable = Customer;
|
||||
|
||||
// Noun-phrase page caption: Title Case.
|
||||
Caption = 'Sales Orders';
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
// Sentence-phrase action caption: Sentence case.
|
||||
action(PostAndPrint)
|
||||
{
|
||||
Caption = 'Post and print';
|
||||
ApplicationArea = All;
|
||||
}
|
||||
|
||||
action(SendEmail)
|
||||
{
|
||||
Caption = 'Send email';
|
||||
ApplicationArea = All;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [caption, capitalization, title-case, sentence-case, noun-phrase]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Capitalize captions by phrase type: noun phrase is Title Case, sentence phrase is Sentence case
|
||||
|
||||
## Description
|
||||
|
||||
Business Central UI captions follow a simple capitalization rule that depends on the grammatical shape of the caption, not its location. A caption that is a pure noun phrase — no verb — uses Title Case: each major word capitalized (`Sales Orders`, `Chart of Accounts`, `Payment Terms`). A caption that is an imperative or declarative sentence phrase — contains a verb — uses Sentence case: only the first word and proper nouns capitalized (`Post and print`, `Send email`, `Create flow`). Following the rule makes unrelated captions feel consistent; ignoring it is visibly inconsistent in the user's navigation.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Decide by parsing the caption as a phrase. "Sales Orders" is a thing; Title Case. "Post and print" tells the user to do something; Sentence case. For captions that are literally a single noun (`Save`, `Close`), treat them as sentence phrases — the imperative verb is implied.
|
||||
|
||||
See sample: `caption-capitalization-noun-phrase-vs-sentence-phrase.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Writing `Caption = 'Sales orders'` on a list page (noun phrase styled as a sentence) or `Caption = 'Post And Print'` on an action (sentence phrase styled as title case). Both read as typos to a native English reader and inconsistent to a translator.
|
||||
|
||||
See sample: `caption-capitalization-noun-phrase-vs-sentence-phrase.bad.al`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [control-add-in, javascript, accessibility, framework, wcag]
|
||||
technologies: [al, javascript]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Control add-in accessibility is the developer's responsibility
|
||||
|
||||
## Description
|
||||
|
||||
When a developer builds a JavaScript control add-in, they bypass the Business Central framework's built-in accessibility support and take full responsibility for the accessibility of the rendered HTML, JavaScript, and CSS. Unlike standard AL page controls, an add-in receives no automatic ARIA semantics, no automatic keyboard handling, and no automatic high-contrast support from the BC client.
|
||||
|
||||
Control add-in code must be reviewed for WCAG 2.1 AA compliance and general accessibility best practices. Automated review is inherently non-exhaustive — many accessibility issues (keyboard flow, screen reader announcements, dynamic behavior) require manual testing.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Treat every UI-rendering change to a control add-in as something the platform will not catch for you: accessible names, semantic HTML, keyboard reachability, focus management, contrast, and reflow are all yours to verify. When reporting issues in control add-in code, include a recommendation that a manual accessibility review accompany any control add-in that renders a UI.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [control-add-in, color-tokens, theming, high-contrast, forced-colors, accessibility]
|
||||
technologies: [al, javascript]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Control add-ins cannot use BC color tokens or theming
|
||||
|
||||
## Description
|
||||
|
||||
A JavaScript control add-in has no access to Business Central's color tokens or theming system. The BC client will not push theme variables, accent colors, or high-contrast palettes into the add-in's iframe. As a result, the add-in must handle Windows contrast themes independently — for example by responding to the `forced-colors` CSS media query or an equivalent mechanism, and by ensuring its own contrast ratios meet WCAG AA (4.5:1 for normal text, 3:1 for large text and UI components) against the backgrounds it draws.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Style control add-ins with explicit colors that are known to meet contrast requirements, and add a `forced-colors` (or equivalent) branch so that Windows high-contrast users see a usable rendering. Do not assume that the add-in inherits BC's theme — verify the rendered output in default, dark, and high-contrast themes.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [style, cosmetic, attention, strong, subordinate, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Cosmetic styles need no textual context
|
||||
|
||||
## Description
|
||||
|
||||
A field's `Style` property controls text formatting. Some style values are purely **cosmetic** — they change visual appearance but do not convey semantic meaning. Cosmetic styles never require additional context and must not be reported as accessibility findings:
|
||||
|
||||
- `None`, `Standard`
|
||||
- `StandardAccent` (Blue)
|
||||
- `Strong` (Bold), `StrongAccent` (Blue + Bold)
|
||||
- `Attention` (Red + Italic), `AttentionAccent` (Blue + Italic)
|
||||
- `Subordinate` (Grey)
|
||||
|
||||
This list is exhaustive — every other named style on the platform either falls outside the cosmetic set or is one of the three semantic styles documented in `semantic-styles-need-independent-textual-meaning.md`.
|
||||
|
||||
The same rule applies whether the cosmetic style is set via `Style` directly or via a `StyleExpr` Text variable. If the resolved value at runtime is one of the cosmetic styles above, the field is safe.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Use cosmetic styles freely for visual emphasis. Do not treat the use of `Attention`, `Strong`, or any other cosmetic value as an accessibility issue — the colors and weights are purely presentational and carry no meaning a screen reader needs to convey.
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
page 51003 "UI Sample FieldTooltip Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
group(General)
|
||||
{
|
||||
field("Name"; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
// No "Specifies" opener, no period, a bare fragment.
|
||||
ToolTip = 'The name of the customer';
|
||||
}
|
||||
field("Balance (LCY)"; Rec."Balance (LCY)")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ToolTip = 'Balance';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
page 51002 "UI Sample FieldTooltip Good"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
group(General)
|
||||
{
|
||||
field("Name"; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ToolTip = 'Specifies the name of the customer.';
|
||||
}
|
||||
field("Balance (LCY)"; Rec."Balance (LCY)")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ToolTip = 'Shows the current balance in the local currency.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [tooltip, field, specifies, voice, period]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Field tooltips start with "Specifies" and end with a period
|
||||
|
||||
## Description
|
||||
|
||||
Field tooltips describe what a value means, and the Business Central house style for them is a declarative sentence that starts with "Specifies" and ends with a period. The convention is not cosmetic: it yields a consistent voice across thousands of fields so a user scanning several tooltips in quick succession can compare them without re-parsing each opening clause. Alternative phrasings ("Shows …", "The …") are accepted when they describe the field clearly, but "Specifies …" is the default and the easiest to translate consistently.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Write field tooltips as `Specifies <what the field represents>.` — a single sentence, Sentence case, terminating period. Keep under the ~250-character tooltip budget (see `respect-ui-text-character-limits`). When the field's meaning is genuinely not a "specifies" sentence, use "Shows …" or a clearly descriptive alternative; avoid bare fragments.
|
||||
|
||||
See sample: `field-tooltips-start-with-specifies-and-end-with-period.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`ToolTip = 'The name of the customer'` — missing "Specifies" opener, missing period. `ToolTip = 'Customer name'` — a fragment rather than a sentence. Both sit inconsistently next to adjacent "Specifies …" tooltips on the same page.
|
||||
|
||||
See sample: `field-tooltips-start-with-specifies-and-end-with-period.bad.al`.
|
||||
|
|
@ -1,25 +1,30 @@
|
|||
page 50732 "UI Grid Good"
|
||||
page 50207 "UI Sample Data Table"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
grid(BalanceGrid)
|
||||
grid(DataGrid)
|
||||
{
|
||||
GridLayout = Columns;
|
||||
group(CustomerColumn)
|
||||
group(Column1)
|
||||
{
|
||||
ShowCaption = false;
|
||||
field(CustomerName; Rec."Customer Name")
|
||||
field(Name; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
group(BalanceColumn)
|
||||
group(Column2)
|
||||
{
|
||||
ShowCaption = false;
|
||||
field(Balance; Rec.Balance)
|
||||
field(Balance; Rec."Balance (LCY)")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
28
microsoft/knowledge/ui/grid-data-table-heuristic.md
Normal file
28
microsoft/knowledge/ui/grid-data-table-heuristic.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [grid, fixed, data-table, heuristic, show-caption, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Grid and fixed-layout data-table heuristic
|
||||
|
||||
## Description
|
||||
|
||||
Business Central renders `grid()` and `fixed()` layouts in two modes. The mode is chosen automatically by a client heuristic. A grid renders as a **data table** (HTML `<table>` with row/column semantics) only when **all** of the following are true:
|
||||
|
||||
- All direct children of the grid/fixed are groups (no loose fields).
|
||||
- Every child of every group is a field (no nested groups or other controls).
|
||||
- All fields have `ShowCaption = false`.
|
||||
|
||||
The heuristic checks field captions only — group `ShowCaption` is not part of the check. A group with a visible caption inside a data-table grid does **not** break the heuristic and is not a violation. However, groups in a data table should also have `ShowCaption = false` for correct visual presentation.
|
||||
|
||||
Any grid or fixed layout that does not meet all three conditions renders as a layout table (visual column arrangement, no table semantics).
|
||||
|
||||
## Best Practice
|
||||
|
||||
If you intend a grid or fixed layout to render as a data table, satisfy all three conditions and verify the resulting markup matches your intent. If you do not need tabular semantics, prefer simple groups over grid or fixed layouts — they reflow better and produce correct semantic markup automatically.
|
||||
|
||||
See sample: `grid-data-table-heuristic.good.al`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [group, caption, missing, duplicate, generic, accessibility, false-positive]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Group caption quality is not an accessibility issue
|
||||
|
||||
## Description
|
||||
|
||||
Group captions affect page organization, but missing, generic, or duplicate group captions are **not** accessibility violations per the BC accessibility rules. Do not flag groups for missing, generic, or duplicate captions during an accessibility review.
|
||||
|
||||
This rule prevents a common false positive: LLM-driven reviewers tend to flag "GroupName" or duplicated `Caption = 'General'` as accessibility issues, but the BC client does not depend on group captions for screen-reader announcements of the fields within. Caption quality belongs to other review domains (UI text / style), not accessibility.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Treat group caption quality as a UI-text concern reviewed elsewhere. Accessibility findings on groups should be limited to the specific patterns documented in the `grid-data-table-heuristic.md`, `tabular-intent-requires-data-table-conditions.md`, and `group-labeled-first-child-exception.md` files.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
page 50204 "UI Sample First Child Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
group(SomeGroup)
|
||||
{
|
||||
ShowCaption = false;
|
||||
field(DescriptionField; Rec.Address)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
MultiLine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
page 50730 "UI Caption Good"
|
||||
page 50203 "UI Sample First Child Good"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
|
|
@ -7,15 +10,13 @@ page 50730 "UI Caption Good"
|
|||
group(Description)
|
||||
{
|
||||
Caption = 'Description';
|
||||
field(DescriptionField; Rec.Description)
|
||||
field(DescriptionField; Rec.Address)
|
||||
{
|
||||
MultiLine = true;
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
MultiLine = true;
|
||||
}
|
||||
}
|
||||
field(CustomerName; Rec."Customer Name")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [show-caption, group, first-child, multiline, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Group-labeled first child exception
|
||||
|
||||
## Description
|
||||
|
||||
`ShowCaption = false` is acceptable on an editable field only when **all** of the following conditions are met:
|
||||
|
||||
1. The control is the **first visible field** in its parent group.
|
||||
2. The field has `ShowCaption = false`.
|
||||
3. The parent group has a visible caption: `ShowCaption` is true (the default) **and** the group has a non-empty `Caption` value.
|
||||
|
||||
When these three conditions hold, the group caption becomes the accessible label for the field. This works regardless of whether the field is multiline. The presence of `InstructionalText` on the field is irrelevant to this check.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Do not second-guess this exception. If the three conditions are met, the pattern is acceptable — even if the group caption seems generic (e.g. "General Information") or does not exactly match the field name.
|
||||
|
||||
See sample: `group-labeled-first-child-exception.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
If the parent group has `ShowCaption = false` or no `Caption`, the first-child exception does not apply: the field has no accessible label anywhere.
|
||||
|
||||
See sample: `group-labeled-first-child-exception.bad.al`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [group, show-caption, card, document, layout, accessibility, false-positive]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Group ShowCaption = false outside grid/fixed is a layout choice
|
||||
|
||||
## Description
|
||||
|
||||
In a standard Card or Document page, a group with `ShowCaption = false` is a layout choice, not an accessibility violation. Only flag `ShowCaption` issues as documented in the grid/fixed-layout and field-level `ShowCaption` rules — `show-caption-on-editable-fields.md`, `grid-data-table-heuristic.md`, `tabular-intent-requires-data-table-conditions.md`.
|
||||
|
||||
The heuristic in BC's client uses **field** captions to decide between data-table and layout-table rendering. A captionless group (outside a grid or fixed layout) does not strip labels from its child fields — each field retains its own caption.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Reserve accessibility findings for hidden **field** labels and grid-semantics problems. Do not raise a finding merely because a `group` block has `ShowCaption = false` in an ordinary Card or Document page layout.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
page 50731 "UI Caption Bad"
|
||||
{
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field(CustomerName; Rec."Customer Name")
|
||||
{
|
||||
InstructionalText = 'Enter the customer name.';
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [showcaption, editable, accessibility, screen-reader, label]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Keep captions on editable fields
|
||||
|
||||
## Description
|
||||
|
||||
`ShowCaption = false` on an editable page field removes the visible and accessible label that identifies the input. `InstructionalText` is not a replacement: it behaves like placeholder text, disappears after entry, and is not reliably announced as the field label. The default `ShowCaption = true` is the safe form-field pattern.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Leave captions visible on editable fields. `ShowCaption = false` is acceptable for non-editable content fields, for fields inside a valid data-table grid pattern, and for the first visible field in a parent group with a visible non-empty caption; in that last pattern, the group caption becomes the accessible label.
|
||||
|
||||
See sample: `keep-captions-on-editable-fields.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Hiding the caption on an editable field because the page layout looks cleaner, or because `InstructionalText` appears to describe the input. Screen reader users lose the field label, and sighted users lose the persistent visual cue.
|
||||
|
||||
See sample: `keep-captions-on-editable-fields.bad.al`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [grid, fixed, layout-table, show-caption, false-positive, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Layout-table grids with visible captions are valid
|
||||
|
||||
## Description
|
||||
|
||||
A grid or fixed layout that does not meet all three data-table conditions renders as a **layout table**. A layout table where editable fields keep their visible captions is not an accessibility violation. Each field is labeled by its own caption — this is a valid, accessible pattern.
|
||||
|
||||
Do not flag a grid or fixed layout as an accessibility issue merely because it does not meet the data-table heuristic. The violation is hidden labels in a non-data-table grid, not the layout choice itself.
|
||||
|
||||
## Best Practice
|
||||
|
||||
When reviewing a grid or fixed layout, first check whether it meets all data-table conditions. If yes, `ShowCaption = false` on fields is correct. If no, allow editable fields to keep their captions and only flag the cases enumerated in `tabular-intent-requires-data-table-conditions.md`.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [control-addin, javascript, accessibility, wcag, keyboard, aria]
|
||||
technologies: [al, javascript]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Manually review UI-rendering control add-in changes for accessibility
|
||||
|
||||
## Description
|
||||
|
||||
JavaScript control add-ins bypass much of the Business Central client's built-in accessibility support. Once the add-in renders its own HTML, JavaScript, or CSS, the extension owns WCAG 2.1 AA concerns such as accessible names, semantic HTML, keyboard navigation, color contrast, focus management, and 200% zoom/reflow. Automated review cannot exhaustively verify those behaviours.
|
||||
|
||||
## Best Practice
|
||||
|
||||
When a control add-in change touches DOM creation, templates, CSS, interaction handlers, ARIA attributes, dynamic visibility, or focus flow, include a manual accessibility review finding even if no specific defect is obvious. Do not require manual accessibility review for pure data processing or API changes that do not render UI.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Treating a control add-in diff as clean because no AL page properties changed. A new `div`-based button without an accessible name, a keyboard trap, or a color-only status indicator lives in JavaScript and still affects Business Central users.
|
||||
33
microsoft/knowledge/ui/no-nested-grids.bad.al
Normal file
33
microsoft/knowledge/ui/no-nested-grids.bad.al
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
page 50210 "UI Sample Nested Grid Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
grid(OuterGrid)
|
||||
{
|
||||
GridLayout = Columns;
|
||||
group(Left)
|
||||
{
|
||||
ShowCaption = false;
|
||||
grid(InnerGrid)
|
||||
{
|
||||
GridLayout = Rows;
|
||||
group(Row1)
|
||||
{
|
||||
ShowCaption = false;
|
||||
field(Name; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
microsoft/knowledge/ui/no-nested-grids.md
Normal file
22
microsoft/knowledge/ui/no-nested-grids.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [grid, nested-grid, fixed, data-table, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Nested grids are not supported
|
||||
|
||||
## Description
|
||||
|
||||
A grid nested inside another grid is not a supported pattern in Business Central. Even if an inner grid independently meets the data-table heuristic, the outer grid fails because its groups contain non-field children (the inner grids). The result is broken table semantics for both layers.
|
||||
|
||||
Always flag a nested grid as a violation. The fix is to restructure the page so there is at most one grid in any branch of the layout tree, choosing either a data-table or a layout-table arrangement.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Wrapping a working data-table grid inside another grid in an attempt to compose two tabular regions side by side. The outer grid silently degrades to layout-table rendering, the inner grid's headers are no longer associated with the outer structure, and editable fields with `ShowCaption = false` lose their labels.
|
||||
|
||||
See sample: `no-nested-grids.bad.al`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [on-drill-down, link, non-editable, accessibility, false-positive]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# OnDrillDown on non-editable fields renders as a link
|
||||
|
||||
## Description
|
||||
|
||||
The Business Central client renders non-editable fields that have an `OnDrillDown` trigger as HTML `<a>` (anchor) elements. Screen readers correctly announce these as links. `OnDrillDown` on a non-editable field is therefore **not** an accessibility issue — the platform handles the semantics.
|
||||
|
||||
Do not flag `OnDrillDown` usage as an accessibility issue. The combination of `Editable = false` and `OnDrillDown` is the standard BC pattern for navigable, screen-reader-friendly value cells in list and card pages.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Use `OnDrillDown` freely on non-editable fields when you want users to navigate from a value to a related record or detail page. No additional ARIA attributes or accessible-name workarounds are required.
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
page 50735 "UI Style Bad"
|
||||
{
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field(Score; Score)
|
||||
{
|
||||
Caption = 'Score';
|
||||
Style = Favorable;
|
||||
StyleExpr = IsGood;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
Score: Integer;
|
||||
IsGood: Boolean;
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
page 50734 "UI Style Good"
|
||||
{
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field(ValidationStatus; ValidationStatus)
|
||||
{
|
||||
Caption = 'Validation status';
|
||||
Style = Unfavorable;
|
||||
StyleExpr = HasValidationErrors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
ValidationStatus: Text;
|
||||
HasValidationErrors: Boolean;
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [style, styleexpr, favorable, unfavorable, ambiguous, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Provide text meaning for semantic styles
|
||||
|
||||
## Description
|
||||
|
||||
Most Business Central page styles are cosmetic, but `Favorable`, `Unfavorable`, and `Ambiguous` communicate meaning through color. Color-only meaning is not accessible. A user who cannot perceive the style must still be able to determine whether the value is positive, negative, or uncertain from the caption, value, or nearby text.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Use semantic styles only when the meaning is independently available: a caption such as "Error", a value such as "Failed", a signed number whose sign carries the meaning, or an adjacent status field. Cosmetic styles such as `Strong`, `Attention`, and `Subordinate` do not need this extra check. Cue tiles inside `cuegroup` are exempt because the client supplies accessible semantic labels.
|
||||
|
||||
See sample: `provide-text-meaning-for-semantic-styles.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Applying `Style = Favorable`, `Unfavorable`, or `Ambiguous` to a value whose text is neutral, such as "42" or "Open", without any caption or adjacent field explaining what the color means.
|
||||
|
||||
See sample: `provide-text-meaning-for-semantic-styles.bad.al`.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [caption, tooltip, character-limit, truncation, localization]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Respect Business Central's UI text character limits to avoid truncation
|
||||
|
||||
## Description
|
||||
|
||||
Business Central UI surfaces have practical character limits before the platform truncates or the translator's localization overflows the available space. Authoring captions and tooltips close to the English limit almost guarantees truncation in languages whose translations are longer (German, French, Spanish average 20–40% longer than English). The limits are not hard compiler errors — they are product-quality thresholds that agents should flag at author time so the string reaches localization with room to grow.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Author within these approximate limits (English): action and field captions ~40 chars; field-group, menu-item, page, and dialog titles ~40 chars; button captions ~20 chars; action and field tooltips ~250 chars; dialog text and error messages ~250 chars; notifications ~100 chars; checklist ShortTitleChecklist 34, LongerTitleCard 53, CardDescription 180. Leave headroom for longer translations; at 40/40 in English, German is likely to truncate.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`action(RecalculateAndReapplyAllOutstandingCustomerDiscounts) { Caption = 'Recalculate and reapply all outstanding customer discounts'; }` — 58 characters in English, essentially guaranteed to truncate once translated. The fix is to shorten the English caption (`Recalculate customer discounts`, 30 chars) and move the full sentence into the tooltip where the budget is larger.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
page 50213 "UI Sample CueGroup Style"
|
||||
{
|
||||
PageType = RoleCenter;
|
||||
|
||||
layout
|
||||
{
|
||||
area(RoleCenter)
|
||||
{
|
||||
cuegroup(Activities)
|
||||
{
|
||||
Caption = 'Activities';
|
||||
field(OverdueInvoices; OverdueInvoiceCount)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Overdue Invoices';
|
||||
Style = Unfavorable;
|
||||
}
|
||||
field(PaidInvoices; PaidInvoiceCount)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Paid Invoices';
|
||||
Style = Favorable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
OverdueInvoiceCount: Integer;
|
||||
PaidInvoiceCount: Integer;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [style, cuegroup, cue-tile, favorable, unfavorable, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Semantic styles in a cuegroup are auto-labeled
|
||||
|
||||
## Description
|
||||
|
||||
Fields inside a `cuegroup` render as cue tiles. The Business Central client automatically provides an accessible label for semantic styles on cue tiles (for example, "Favorable", "Unfavorable"). Semantic styles in a `cuegroup` therefore do **not** need additional context and should be ignored when checking that semantic colors are backed by text.
|
||||
|
||||
This is a narrow platform exception to `semantic-styles-need-independent-textual-meaning.md`. Outside a `cuegroup`, the normal rule applies.
|
||||
|
||||
## Best Practice
|
||||
|
||||
You may apply `Favorable`, `Unfavorable`, or `Ambiguous` to fields inside a `cuegroup` without supplying a redundant textual indicator — the platform supplies the screen-reader text. Reserve this shortcut for cue tiles only; do not extend it to other layout containers.
|
||||
|
||||
See sample: `semantic-style-in-cuegroup-exception.good.al`.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
page 50212 "UI Sample Semantic Style Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field(CompanyName; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Style = Favorable;
|
||||
}
|
||||
field(Confidence; ConfidencePercent)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Confidence';
|
||||
StyleExpr = ConfidenceStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
ConfidencePercent: Decimal;
|
||||
ConfidenceStyle: Text;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
page 50211 "UI Sample Semantic Style Good"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Cust. Ledger Entry";
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field(OverdueAmount; Rec."Remaining Amount")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Overdue Amount';
|
||||
Style = Unfavorable;
|
||||
}
|
||||
field(ProfitMargin; Rec.Amount)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Profit Margin';
|
||||
Style = Favorable;
|
||||
StyleExpr = IsProfitable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
IsProfitable: Boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [style, favorable, unfavorable, ambiguous, color, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Semantic styles need independent textual meaning
|
||||
|
||||
## Description
|
||||
|
||||
Three `Style` values carry semantic meaning through color and must be backed by text that conveys the same meaning:
|
||||
|
||||
- `Favorable` (Bold + Green) — implies a positive outcome.
|
||||
- `Unfavorable` (Bold + Italic + Red) — implies a negative outcome.
|
||||
- `Ambiguous` (Yellow) — implies an uncertain or mixed outcome.
|
||||
|
||||
For accessibility, assume the style is completely invisible to the user. The semantic meaning must be independently determinable from at least one of:
|
||||
|
||||
1. The **field caption** matches the semantic meaning (e.g. caption "Error" with `Style = Unfavorable`, or "Profit" with `Style = Favorable`).
|
||||
2. The **field value** communicates the meaning (e.g. value "Success!" with Favorable, a negative number with Unfavorable).
|
||||
3. An **adjacent field** provides a textual representation of the semantic meaning (e.g. a "Status" column reads "High" / "Medium" / "Low" alongside a percentage field).
|
||||
|
||||
The rule applies equally whether `Style` is set to a literal value or to a variable that evaluates to a semantic style at runtime.
|
||||
|
||||
## Best Practice
|
||||
|
||||
When you reach for `Favorable`, `Unfavorable`, or `Ambiguous`, verify that the caption, value, or an adjacent column already conveys the same meaning. See sample: `semantic-styles-need-independent-textual-meaning.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Applying a semantic style for purely cosmetic emphasis (e.g. green company name for aesthetics), or using semantic colors where only the color reveals the threshold (e.g. confidence percentages with no qualitative label). See sample: `semantic-styles-need-independent-textual-meaning.bad.al`.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
page 50202 "UI Sample NonEditable Caption"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field("Customer Name"; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Editable = false;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [show-caption, non-editable, content, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# ShowCaption = false on non-editable fields
|
||||
|
||||
## Description
|
||||
|
||||
When a field is explicitly non-editable (`Editable = false`), it serves as content rather than as a form field. In that case, `ShowCaption = false` is acceptable: there is no input control whose label could be lost. The combination signals to a reviewer (and to the platform) that the field displays a value standalone — for example a status message or a description that is meaningful on its own.
|
||||
|
||||
This exception does **not** extend to dynamically editable fields. A field with `Editable = SomeBooleanExpression` may be editable at runtime and must keep its caption.
|
||||
|
||||
## Best Practice
|
||||
|
||||
If you want to hide a field's caption, pair `ShowCaption = false` with a literal `Editable = false`. Use this pattern only for content fields that do not act as labels for other fields in the same layout container.
|
||||
|
||||
See sample: `show-caption-false-allowed-on-non-editable-fields.good.al`.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
page 50206 "UI Sample PromptDialog"
|
||||
{
|
||||
PageType = PromptDialog;
|
||||
Caption = 'Draft new project with Copilot';
|
||||
|
||||
layout
|
||||
{
|
||||
area(Prompt)
|
||||
{
|
||||
field(ProjectDescription; InputProjectDescription)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
MultiLine = true;
|
||||
InstructionalText = 'Describe the project';
|
||||
}
|
||||
}
|
||||
area(Content)
|
||||
{
|
||||
field("Job Description"; JobDescription)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Project Description';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
InputProjectDescription: Text;
|
||||
JobDescription: Text;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [show-caption, promptdialog, copilot, prompt, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# ShowCaption in a PromptDialog prompt area
|
||||
|
||||
## Description
|
||||
|
||||
On `PageType = PromptDialog` pages, input fields inside `area(Prompt)` are labeled by the dialog's heading — the page `Caption`. Setting `ShowCaption = false` on such an input field is the standard pattern and should not be flagged, provided the page has a `Caption`.
|
||||
|
||||
Fields in the `area(Content)` section of the same PromptDialog page are **not** labeled by the dialog heading and follow the normal `ShowCaption` rules.
|
||||
|
||||
## Best Practice
|
||||
|
||||
In a PromptDialog, give the page a meaningful `Caption` (the dialog heading) and let prompt-area input fields hide their own captions. Treat content-area fields like any other editable field — keep their captions.
|
||||
|
||||
See sample: `show-caption-in-promptdialog-prompt-area.good.al`.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
page 50205 "UI Sample Repeater"
|
||||
{
|
||||
PageType = List;
|
||||
SourceTable = "Sales Line";
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
repeater(Lines)
|
||||
{
|
||||
field(Description; Rec.Description)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
}
|
||||
field(Amount; Rec.Amount)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
microsoft/knowledge/ui/show-caption-in-repeater-allowed.md
Normal file
22
microsoft/knowledge/ui/show-caption-in-repeater-allowed.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [show-caption, repeater, column-header, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# ShowCaption inside a repeater is harmless
|
||||
|
||||
## Description
|
||||
|
||||
Fields inside a `repeater()` control are labeled by their **column headers**, not by their own captions. `ShowCaption = false` on a field inside a repeater is harmless and should not be flagged.
|
||||
|
||||
This is the explicit behaviour of the Business Central client: a repeater renders as a tabular list whose column headings come from each field's `Caption` (or source-table caption), and individual row cells do not announce a per-cell caption.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Inside a repeater, you may set `ShowCaption = false` on fields without losing accessibility. The column header still provides the label for every cell in that column. Outside a repeater, the rules in `show-caption-on-editable-fields.md` apply.
|
||||
|
||||
See sample: `show-caption-in-repeater-allowed.good.al`.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
page 50201 "UI Sample Editable Caption Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field("Customer Name"; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
ShowCaption = false;
|
||||
InstructionalText = 'Enter the customer name';
|
||||
}
|
||||
field("Dynamic Editable"; Rec."No.")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Editable = IsEditable;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
IsEditable: Boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
page 50200 "UI Sample Editable Caption Good"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field("Customer Name"; Rec.Name)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
microsoft/knowledge/ui/show-caption-on-editable-fields.md
Normal file
28
microsoft/knowledge/ui/show-caption-on-editable-fields.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [show-caption, editable, accessibility, label, instructional-text]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# ShowCaption on editable fields
|
||||
|
||||
## Description
|
||||
|
||||
`ShowCaption` must remain true (the default) on editable fields unless the field matches one of the officially supported "magic patterns". Fields are editable by default. Setting `ShowCaption = false` on an editable field is almost always an accessibility bug: without a visible caption, screen reader users lose the label that identifies the field, and sighted users lose a visual cue.
|
||||
|
||||
A field whose `Editable` property is a Boolean expression (e.g. `Editable = IsEditable`) is dynamically editable and must be treated as a form field — `ShowCaption = false` on such a field is also a violation.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Leave `ShowCaption` at its default on editable fields. If a caption would be visually redundant, rely on one of the documented magic patterns (group-labeled first child, repeater column, PromptDialog prompt input) rather than removing the caption.
|
||||
|
||||
See sample: `show-caption-on-editable-fields.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
The `InstructionalText` property on a field renders as HTML placeholder text and is **not** a substitute for a caption — it disappears once the user types and is not reliably announced by screen readers.
|
||||
|
||||
See sample: `show-caption-on-editable-fields.bad.al`.
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
page 50208 "UI Sample Standalone Content"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
grid(InfoGrid)
|
||||
{
|
||||
GridLayout = Columns;
|
||||
group(LeftColumn)
|
||||
{
|
||||
field(Address; Rec.Address)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
}
|
||||
field(City; Rec.City)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
}
|
||||
}
|
||||
group(RightColumn)
|
||||
{
|
||||
field(StatusMessage; StatusText)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Editable = false;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
StatusText: Text;
|
||||
}
|
||||
22
microsoft/knowledge/ui/standalone-content-in-layout-table.md
Normal file
22
microsoft/knowledge/ui/standalone-content-in-layout-table.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [grid, fixed, layout-table, standalone-content, show-caption, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Standalone content in a layout-table grid
|
||||
|
||||
## Description
|
||||
|
||||
A non-editable field with `ShowCaption = false` is acceptable inside a layout-table grid **only when** the field is **standalone content** — it displays a value that is meaningful on its own (for example a status message or a description) and is **not** intended to label or be labeled by another field in the grid.
|
||||
|
||||
Layout tables have no `<th>` column headers, so a captionless field that is meant to participate in a tabular relationship with a neighbour has no accessible label at all.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Reserve `ShowCaption = false` in a layout-table grid for non-editable, free-standing content cells. If a field's role is to label or annotate another field in the same grid, restructure the grid to meet the data-table conditions (see `grid-data-table-heuristic.md`) instead of hiding the caption.
|
||||
|
||||
See sample: `standalone-content-in-layout-table.good.al`.
|
||||
41
microsoft/knowledge/ui/style-expr-text-vs-boolean.good.al
Normal file
41
microsoft/knowledge/ui/style-expr-text-vs-boolean.good.al
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
page 50214 "UI Sample StyleExpr"
|
||||
{
|
||||
PageType = List;
|
||||
SourceTable = "Sales Header";
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
repeater(Lines)
|
||||
{
|
||||
field(Status; Rec.Status)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
StyleExpr = StatusStyle;
|
||||
}
|
||||
field(Amount; Rec.Amount)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Style = Favorable;
|
||||
StyleExpr = IsProfitable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger OnAfterGetRecord()
|
||||
begin
|
||||
case Rec.Status of
|
||||
Rec.Status::Open:
|
||||
StatusStyle := 'Standard';
|
||||
Rec.Status::Released:
|
||||
StatusStyle := 'Favorable';
|
||||
end;
|
||||
IsProfitable := Rec.Amount > 0;
|
||||
end;
|
||||
|
||||
var
|
||||
StatusStyle: Text;
|
||||
IsProfitable: Boolean;
|
||||
}
|
||||
25
microsoft/knowledge/ui/style-expr-text-vs-boolean.md
Normal file
25
microsoft/knowledge/ui/style-expr-text-vs-boolean.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [style-expr, style, boolean, text-variable, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# StyleExpr: Boolean toggle vs Text variable
|
||||
|
||||
## Description
|
||||
|
||||
`StyleExpr` on a page field serves two distinct purposes depending on its type:
|
||||
|
||||
- **Boolean** — When `StyleExpr` is a Boolean expression, it controls whether the `Style` property is applied. In this case the `Style` property carries the style name; analyze `Style` and ignore `StyleExpr` itself.
|
||||
- **Text** — When `StyleExpr` is a Text variable (e.g. `StyleExpr = StatusStyle` where `StatusStyle: Text` and is assigned literals such as `'Favorable'`), the variable contains the style name at runtime. There may be no `Style` property at all — the `StyleExpr` variable **is** the style.
|
||||
|
||||
When `StyleExpr` is Text, you must trace the variable's assignments — typically in `OnAfterGetRecord` or `OnAfterGetCurrRecord` — to determine which styles can be applied, then apply the same accessibility rules as for a literal `Style` value.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Inspect the declared type of the symbol referenced by `StyleExpr` before drawing conclusions. If it is Boolean, evaluate the `Style` property. If it is Text, follow every assignment to the variable and check the full set of possible style values against `cosmetic-styles-need-no-textual-context.md` and `semantic-styles-need-independent-textual-meaning.md`.
|
||||
|
||||
See sample: `style-expr-text-vs-boolean.good.al`.
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
page 50209 "UI Sample Tabular Mix Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Cust. Ledger Entry";
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
grid(StatementGrid)
|
||||
{
|
||||
GridLayout = Columns;
|
||||
group(Periods)
|
||||
{
|
||||
ShowCaption = false;
|
||||
field(StatementPeriod; Rec."Posting Date")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Editable = false;
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
group(Balances)
|
||||
{
|
||||
ShowCaption = false;
|
||||
field(StatementBalance; Rec.Amount)
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Editable = false;
|
||||
ShowCaption = false;
|
||||
}
|
||||
field(DueDate; Rec."Due Date")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [grid, fixed, tabular-intent, data-table, accidental-mix, accessibility]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Tabular intent requires data-table conditions
|
||||
|
||||
## Description
|
||||
|
||||
The most common accessibility bug in grid layouts is partially following the data-table conventions. A developer arranges fields with **tabular intent** — one field acts as a label or row header for another — but the grid does not satisfy all the data-table heuristic conditions. The client falls back to layout-table rendering, and the tabular relationships between fields are lost: a screen reader announces each field independently with no programmatic association.
|
||||
|
||||
Flag a grid as an accessibility issue when any of these are true:
|
||||
|
||||
- An editable field has `ShowCaption = false` and the grid does not meet all data-table conditions.
|
||||
- Fields are arranged so that one field is clearly intended to label or describe another field (tabular data intent), but the grid does not meet all data-table conditions.
|
||||
|
||||
Both manifestations have the same root cause: tabular semantics were intended but the heuristic ultimately rendered the grid as a layout table.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
A single field that keeps its visible caption is enough to demote an entire would-be data-table grid into a layout table — and silently strip the labels off its sibling captionless fields. Either restructure to meet all three conditions, or restore captions on every editable field.
|
||||
|
||||
See sample: `tabular-intent-requires-data-table-conditions.bad.al`.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [title, caption, page, dialog, punctuation, ellipsis]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Titles carry no trailing punctuation and no trailing ellipsis
|
||||
|
||||
## Description
|
||||
|
||||
Page titles, section titles, FastTab titles, and dialog titles in Business Central are labels, not sentences — they have no trailing period, question mark, or exclamation. Trailing ellipsis ("…" or "...") on a title is specifically a long-standing Windows convention for action buttons that open a dialog, and AL handles that via the action's runtime behaviour rather than the caption text. Adding the ellipsis literally into a page caption or action caption is wrong in both directions: the platform also displays its own ellipsis when appropriate, and the static three dots corrupt translations that adjust punctuation for the locale.
|
||||
|
||||
## Best Practice
|
||||
|
||||
End titles with the last word of the title. Sentence case per the capitalization rule for the phrase type (see `caption-capitalization-noun-phrase-vs-sentence-phrase`). If a dialog needs "…" behaviour, rely on the platform; do not type the characters into the caption string.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`Caption = 'Setup wizard...'`, `Caption = 'Sales orders.'`, `page Caption = 'Customer list:'` — all three decorate the title with terminal punctuation that is noise to the reader and a translation headache.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [tooltip, teaching-tip, abouttitle, abouttext, onboarding]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Tooltips describe what a thing is; teaching tips guide what the user can do with it
|
||||
|
||||
## Description
|
||||
|
||||
Business Central exposes two distinct affordances for explaining the UI: ToolTip and the AboutTitle/AboutText teaching tip. They answer different questions and are complementary, not alternatives. ToolTip answers "What is this field/action?" and is expected on every field and action. The teaching tip answers "What can I do with this page or this important element?" and is reserved for the few entry points where an onboarding hint is worth the user's attention. Authors who put teaching-tip content in tooltips make tooltips noisy; authors who put tooltip content in teaching tips make teaching tips useless.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Write ToolTip as a concise descriptive sentence following the "Specifies …" or imperative voice rules. Reserve AboutTitle/AboutText for the top-level card and list pages where first-time users benefit from discovering the page's purpose and outcome. On list pages, title uses the plural form ("About sales invoices"). On card or document pages, title uses the entity name plus "details" ("About sales invoice details").
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
A field ToolTip that tells the user "You can create new customers from here and update their payment terms, and the list also shows…" — that is teaching-tip content. Conversely, an AboutText that simply repeats the page Caption tells the user nothing they did not already read in the title bar.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [tour-tip, abouttext, teaching-tip, imperative, onboarding]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Tour tips describe outcomes, not instructions — never tell the user to perform an action during the tour
|
||||
|
||||
## Description
|
||||
|
||||
A tour is a guided sequence of teaching tips that runs over the page while the user is passively watching. The tour framework does not expose the page's actions during the tip — so an `AboutText` that tells the user `Enter the customer name here.` or `Now post the invoice.` asks the user to do something that is not possible in the moment. The result is a confusing first-run experience. Tour content should describe what the element represents and what the user will be able to do with it after the tour completes, in descriptive rather than imperative voice.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Write tour AboutTitle as a short noun-phrase label for the element ("Who you are selling to", "When all is set, you post"). Write AboutText as one or two sentences that describe the outcome or meaning, not steps. Keep the tour itself short — one to four tips total — and let the regular ToolTip carry the per-element detail.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`AboutText = 'Enter the customer name here.'` on a tour tip — the action is not active. `AboutText = 'Now post the invoice.'` during a tour — the user cannot, and would not want to mid-tour. Both teach nothing and confuse the reader.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
page 51007 "UI Sample Ampersand Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Sales Header";
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
action(PostAndSend)
|
||||
{
|
||||
// '&' is being used as "and", not as an accelerator prefix. The
|
||||
// parser cannot tell; translators re-evaluate every occurrence.
|
||||
Caption = 'Post & Send';
|
||||
ApplicationArea = All;
|
||||
ToolTip = 'Post and send the document.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
page 51006 "UI Sample Ampersand Good"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Sales Header";
|
||||
|
||||
actions
|
||||
{
|
||||
area(Processing)
|
||||
{
|
||||
action(PostAndSend)
|
||||
{
|
||||
// "and" written out. Ampersand-s marks 's' as the accelerator key.
|
||||
Caption = 'Post and &send';
|
||||
ApplicationArea = All;
|
||||
ToolTip = 'Post the document and send it to the customer.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [ampersand, caption, accelerator, translation, voice]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Write "and" in UI captions; keep the ampersand only as an accelerator-key prefix
|
||||
|
||||
## Description
|
||||
|
||||
AL Caption strings use the ampersand character in two distinct ways. Inside a caption, `&` is the accelerator-key prefix — `Caption = '&Post'` underlines the P and makes Alt+P activate the action. Outside that role, `&` is sometimes used as a shortening for the word "and" (`Post & Send`). The first usage is platform-defined and must be preserved. The second is a style choice that the Business Central voice guidelines reject: `Post and send` reads naturally in all supported locales and translates cleanly, while `Post & Send` conveys nothing extra and adds a character that localizers have to re-evaluate.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Use the word "and" in caption text. Keep `&` only when it is immediately followed by a letter chosen as the keyboard accelerator. If both meanings apply, write them explicitly: `Post and &send` uses `s` as the accelerator and spells the conjunction out.
|
||||
|
||||
See sample: `use-and-not-ampersand-in-ui-captions.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`Caption = 'Post & Send'` as the full caption — the ampersand is meant as "and" but the AL parser cannot tell, and the result is inconsistent with every other "X and Y" caption in the product.
|
||||
|
||||
See sample: `use-and-not-ampersand-in-ui-captions.bad.al`.
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
page 50733 "UI Grid Bad"
|
||||
{
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
grid(BalanceGrid)
|
||||
{
|
||||
GridLayout = Columns;
|
||||
field(CustomerName; Rec."Customer Name")
|
||||
{
|
||||
ShowCaption = false;
|
||||
}
|
||||
group(BalanceColumn)
|
||||
{
|
||||
field(Balance; Rec.Balance)
|
||||
{
|
||||
ShowCaption = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: ui
|
||||
keywords: [grid, fixed, showcaption, accessibility, table-semantics]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Use the grid data-table pattern consistently
|
||||
|
||||
## Description
|
||||
|
||||
Business Central `grid` and `fixed` layouts render either as data tables or layout tables based on a structural heuristic. A data table requires all direct children to be groups, every group child to be a field, and all fields to have `ShowCaption = false`. If the structure fails that heuristic, the client renders a layout table; hidden captions on editable fields then remove the only accessible labels.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Use one pattern consistently. For a data-table grid, make every direct child a group and every field `ShowCaption = false`. For a layout grid, keep captions visible on editable or tabular fields and hide captions only on standalone non-editable content where the missing label is not a form-field problem.
|
||||
|
||||
See sample: `use-grid-data-table-pattern-consistently.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Mixing the patterns: one loose field, nested group, or visible field caption prevents data-table rendering, while other editable fields still hide captions. The result looks like a table visually but has layout-table semantics and missing labels for assistive technology.
|
||||
|
||||
See sample: `use-grid-data-table-pattern-consistently.bad.al`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue