Choosing a control type, and writing properties and enum values that the compiler accepts.
describe_control output is the only authority on which properties a control has.
- Discover before you choose
- Interpret property defaults and requirements
- Layout containers
- Data display
- Selection controls —
ItemDisplayTextis a per-item formula - Properties are per-control — never transfer them by analogy
- Control template versions
- Enum type names
- Enum member values
- Option set values
- Color and button-state patterns
- Timer lifecycle
- Read-only ancestors
- Cross-screen navigation
- Semantic display values
- Common property reference
- Troubleshooting
list_controls before planning your layout. Controls
you don't know exist can't influence your design, and the catalog includes high-level
controls (ModernTabList, ModernCard, and others) that are easy to miss and expensive
to reinvent with primitives.
The resulting list will also specify if any Code Components or Canvas Components are available as control instances in the app. The result identifies the ComponentName to pass to describe_control.
Run describe_control on every type you plan to use.
The Default shown for a property is the value the property takes when it is omitted from the YAML.
Properties marked Required: true must be provided, even when no default is shown.
Omit other properties to accept their default.
describe_control results for Canvas and Code Components are snapshots of the current
Studio document, not durable catalog entries. Re-run describe_control for the returned
ComponentName after a successful compile_canvas applies changes to a local component
definition or its custom properties. Do not reuse component descriptions from an earlier
turn after any of those events. Refresh immediately before recording component properties
in a plan or editing a component instance.
| Use Case | Control Type | Variant |
|---|---|---|
| Precise positioning | GroupContainer |
ManualLayout |
| Horizontal responsive layout | GroupContainer |
AutoLayout with LayoutDirection: =LayoutDirection.Horizontal |
| Vertical responsive layout | GroupContainer |
AutoLayout with LayoutDirection: =LayoutDirection.Vertical |
Default to AutoLayout. Use ManualLayout only when the user explicitly requests
pixel-perfect positioning or the app is a fixed-size desktop dashboard. Mobile and
cross-device apps MUST use AutoLayout. See ${PLUGIN_ROOT}/references/LayoutGuide.md for the patterns.
GroupContainer has no OnSelect — it cannot be clicked. This is a common dead
end when building card UI: the container lays out perfectly but tapping it does nothing.
- Clickable cards: use
ModernCardinstead — it hasOnSelectand is designed for it. - Clickable non-card areas: overlay a transparent
ButtonorRectangle(both haveOnSelect) at the same position and size. Set theAppearanceproperty to transparent where available; otherwiseFill: =RGBA(0,0,0,0)andBorderThickness: =0.
ModernCard fills unset slots with placeholder content. It is not an empty
surface. A card that sets only Title renders a large stock photograph above it and the
literal words Subtitle and Description below it, and the photo consumes most of the
card's height — so the value you did set ends up clipped. Nothing about this reaches
compile_canvas.
Set every slot the card displays, and blank the ones you do not want:
- KpiOpenCard:
Control: ModernCard
Properties:
Image: =Blank() # required — otherwise a stock photo appears
HeaderImage: =Blank() # when supported — otherwise header artwork can remain
Title: =CountRows(colTasks) & ""
Subtitle: ="Open tasks"
Description: ="Across all projects"Use only properties returned by describe_control; some card versions expose
HeaderImage, ImageAccessibleLabel and HeaderImageAccessibleLabel. When present,
blank both image slots for a text-only card and set their accessible labels explicitly.
Do not compress Title, Subtitle and Description into a short fixed-height KPI card; let
the card size naturally, reduce the displayed slots, or choose a height that fits them.
When you do want the image, give the card enough Height for the image band plus the
text, and remember that a card used as a gallery row template needs the gallery's
TemplateSize to match.
| Use Case | Control Type | Key Properties |
|---|---|---|
| List of items | Gallery |
Items, TemplateSize, OnSelect |
| Modern tabular data | ModernDataGrid only when its definition can configure columns; otherwise Gallery + header row |
Items, Searchable, Sortable, OnChange |
| Forms | Form |
DataSource, Item, OnSuccess |
ModernDataGrid.Searchable renders its own search input. When the screen already has a
separate search field or filter bar, leave Searchable false or unset and bind the grid's
Items to the external filter formula. Two independent search affordances make it
unclear which filter is active.
ModernDataGridColumn properties are version-specific. Copy the current
describe_control definition into the screen brief.
If the current ModernDataGrid definition exposes no Fields, Columns, or supported
child-column contract, do not create a new grid for a local collection. It can compile
and still render "There are no fields in this data table." Use an explicit header row
plus a Gallery row shell and implement sorting through the Gallery Items formula.
ItemDisplayText and ItemKey on ModernDropdown, ModernCombobox and similar controls
are evaluated once per row with ThisItem in scope. They take an expression, not a column
name in quotes:
# WRONG — every option renders the constant string, or renders blank
Items: =colWorkTypes
ItemDisplayText: ="Value"
# RIGHT — the option shows that row's Value field
Items: =colWorkTypes
ItemDisplayText: =ThisItem.ValueA dropdown whose options all appear empty is almost always this mistake. When Items is
already a single-column table you can omit ItemDisplayText entirely.
Three traps produce most Unknown property errors.
Corner-radius, shadow, and padding properties are not available on every control.
Rectangle in particular has no radius properties:
Unknown property 'RadiusTopLeft' for control type 'Rectangle'.
ModernCard does not have them either — it exposes a single numeric BorderRadius
instead. For a rounded, filled surface with independent corners use a GroupContainer,
which supports all four Radius* properties plus DropShadow and the four Padding*
properties.
The modern React controls and the FluentV9 controls disagree about basic names. This is the most common single-property mistake:
| Intent | ModernText, ModernButton, ModernDropdown, ModernTextInput, ModernNumberInput, PieChart |
Badge |
ModernCard |
|---|---|---|---|
| Text color | Color |
FontColor |
TitleColor / SubtitleColor / DescriptionColor |
| Font size | Size |
FontSize |
TitleSize / SubtitleSize / DescriptionSize |
| Displayed string | Text |
Content |
Title / Subtitle / Description |
| Corner rounding | RadiusTopLeft … RadiusBottomRight |
none | BorderRadius |
Progress is narrower still: no Fill, no Color, no Font*. It is styled through
BasePaletteColor and its three Progress.* enums.
Gallery has Fill but no text properties at all — style the labels inside it, not the
container.
Never write Control: ModernText@1.5.0. Use the bare name that list_controls returned.
Every control type resolves to exactly one template version per app. You do not choose it and you do not need to: write the bare control name everywhere and the app stays consistent.
Writing an explicit @version on even one control breaks that:
Another instance of control type 'ModernText' has already been referenced using a
different version '1.0.0'. All control instances for the same type must currently
reference the same version.
Control type 'ModernText@1.5.0' has a version that is newer than the current version
of '1.0.0'. Using the current version, which may produce errors.
"may produce errors" means the app now binds the older template, and every property that exists only in the newer one is reported as unknown — naming the internal control type, not the one you wrote:
Unknown property 'Color' for control type 'Text'.
Those properties are valid on ModernText. Nothing is wrong with them. The only defect is
the version suffix, and removing every suffix clears the entire cascade in one compile.
- ✅
Control: ModernText - ❌
Control: ModernText@1.5.0 - ❌ mixing
Control: Badgein one file withControl: Badge@1.2.0in another
Never construct an enum type name. Copy it. describe_control prints the exact type
name on the Enum name: line under every enum property. That name is the only correct
one, and it cannot be derived from the control name:
| Control | Property | Enum name: reported by describe_control |
|---|---|---|
ModernDropdown |
Appearance |
Appearance |
ModernTextInput |
Appearance |
Appearance |
ModernNumberInput |
Appearance |
Appearance |
ModernButton |
Appearance |
ButtonAppearance |
Badge |
Appearance |
BadgeCanvas.Appearance |
Badge |
Shape |
BadgeCanvas.Shape |
Badge |
ThemeColor |
BadgeCanvas.ThemeColor |
Progress |
Shape |
Progress.Shape |
Progress |
Thickness |
Progress.Thickness |
ModernNumberInput |
Precision |
DecimalPrecision |
Five controls, one property name, four different enum types. Guessed names like
BadgeAppearance, DropdownAppearance or ProgressBar.ProgressColor all fail with
Name isn't recognized.
Wrap the enum name in ' whenever it contains a dot, a space, or a special
character. Then append .Member:
# Enum name contains a dot -> quote the name, not the member
- StatusBadge:
Control: Badge
Properties:
Appearance: ='BadgeCanvas.Appearance'.Tint
Shape: ='BadgeCanvas.Shape'.Rounded
ThemeColor: ='BadgeCanvas.ThemeColor'.Success
# Plain enum name -> no quoting needed
- ProjectPicker:
Control: ModernDropdown
Properties:
Appearance: =Appearance.UnderlineThemeColor: =Subtle fails with
Name isn't recognized exactly like a wrong type name does. Removing the qualifier is
never the fix — correcting the qualifier is.
The member obeys a separate quoting rule from the type name, and it is the rule that is missed most often.
A member that starts with a digit must be wrapped in '. describe_control reports
these members bare — values: 0, 1, 2, 3, 4, 5, Auto — but that listing is not the
literal you write:
# WRONG — Power Fx parses `DecimalPrecision`, then hits `.1` and stops
Precision: =DecimalPrecision.1
# RIGHT
Precision: =DecimalPrecision.'1'This is not a Name isn't recognized failure. The parser reads the digit as the start of
a new number, so the property emits Expected operator and Expected an operand
together — two messages on one property, naming no enum at all. Seven such properties
produce twenty-one errors that never mention the word "enum".
Every enum whose members are numeric is affected; DecimalPrecision is the one you will
meet most. Whenever a control definition lists members that begin with a digit, write the
quoted form.
Escape an option set name or value with ' when it contains spaces or special characters,
or starts with a number:
# Option set name with a space
- galItemsGallery:
Control: Gallery
Properties:
Items: =Filter(Accounts, 'Account Status' = 'Account Status'.Active)
# Option set with special characters
- lblDueDate:
Control: ModernText
Properties:
Text: =ThisItem.DueDate
Visible: =ThisItem.Status = 'Status (Assignments)'.Active# Color constants
Fill: =Color.White
BasePaletteColor: =Color.Blue
# RGBA
Fill: =RGBA(240, 240, 240, 1)
FontColor: =RGBA(0, 0, 0, 1)
# Conditional color
BasePaletteColor: =If(isActive, Color.Blue, Color.Gray)Fluent appearances own their surface. ButtonAppearance.Secondary, Outline, Subtle
and Transparent can remain light even when Fill is set. Pair those appearances with a
dark Color, or switch to Primary and set BasePaletteColor for a dark surface. Do not
assume Fill overrides the variant.
An automatic Timer needs a start edge after the control exists. If AutoStart: =false
and the Start variable is set true before navigation, the timer can remain at its
initial value until the user clicks it. For timers that gate a workflow:
- Prefer
AutoStart: =true. - On screen entry and on each next-item action, set the running variable false, toggle
Reset, then set running true. - Use
AutoPause: =falseunless manual pause is explicitly required. - Display remaining time, not elapsed time:
Text: =If(varTimerFinished, "00:00", Text(Time(0, 0, Max(0, RoundUp((Self.Duration - Self.Value) / 1000, 0))), "mm:ss")). - Do not use the timer surface as an unlabeled pause button. Add a separate labelled control when pause/resume is required.
DisplayMode is inherited. Do not put row action buttons inside a Gallery or container
set to DisplayMode.View; the descendants become disabled even when they look enabled.
Use Selectable: =false to prevent Gallery selection while leaving the Gallery in
DisplayMode.Edit.
ModernTabList is for tabs that switch panels within one screen. Do not use its
OnChange to navigate between screens: the selected tab can update while Navigate
does not, leaving the highlight and visible screen out of sync. For cross-screen primary
navigation, use a row of ModernButtons whose OnSelect performs the navigation and whose
appearance is derived from the current screen.
Semantic controls need their visible value property. Badge.AccessibleLabel does not
replace Badge.Content; omitting Content can render placeholder text such as AB.
Positioning:
X,Y— position (absolute in ManualLayout)Width,Height— sizeAlign— text alignment (horizontal)VerticalAlign— text alignment (vertical)
Styling:
Fill— background color (absent onBadgeandProgress)Color— text color on the modern React controls;Badgespells itFontColorBasePaletteColor— theme color forBadge,Progress, and the modern inputsSize— font size on the modern React controls;Badgespells itFontSizeFontWeight— Bold, Semibold, Normal, Lighter
Behavior:
DisplayMode— Edit, View, DisabledVisible— boolean visibilityOnSelect— click handlerOnVisible— screen load handler
Layout (AutoLayout):
LayoutDirection— Horizontal or VerticalLayoutAlignItems— Center, Start, End, StretchLayoutJustifyContent— Center, SpaceBetween, Start, EndLayoutGap— spacing between itemsLayoutOverflowY— vertical overflow (Scrollfor scrollable containers)FillPortions— proportional sizingPaddingTop/Bottom/Left/Right— container padding
Unknown property: rundescribe_controland use only the properties it returns for that exact control type.- Many
Unknown propertyerrors naming a control type you never wrote (e.g.'Text'when your YAML saysModernText): a template version conflict. Strip every@versionsuffix from everyControl:value and re-compile. - A property works on one control but not a similar one: property support is per control type, and the modern React, FluentV9 and Classic families disagree. Check the per-control table above rather than reasoning by analogy.
Name isn't recognizedon an enum: the enum type name is wrong. Copy theEnum name:line fromdescribe_controlverbatim; quote it with'if it contains a dot. Deleting the qualifier is not a fix.Expected operatorANDExpected an operandon the same property: an enum member that starts with a digit was written unquoted.Precision: =DecimalPrecision.1must bePrecision: =DecimalPrecision.'1'. Note that this pair of messages names no enum and is notName isn't recognized— see "Enum member values" above.- A
ModernCardrenders a large stock photograph, or the words "Title" / "Subtitle" / "Description": those slots were left unset.ModernCardfills unset slots with placeholder content rather than collapsing them. SetImage,Title,SubtitleandDescriptionfor every slot the card shows, and setImage: =Blank()when the card is meant to be text-only. - Button text is too small: set
SizeonModernButton— but confirm the font property exists on that control first.