APL Style Definition and Evaluation
A style is a named entity that defines a set of visual properties. You can use the style to consistently set those properties on a component. Styles can include conditional logic and can use component state. For example, a style could change text color depending on whether the component's state is checked
.
Style definitions
A style definition specifies the name of the style, a list of one or more parent styles to inherit from, and an ordered list of conditionally-applied property definitions. The following example shows a style definition title suitable for use by the Text component. It sets the size of the font and the color. The color changes based on the state of the Text component and current theme.
"styles": {
"baseText": {
"values": [
{
"fontFamily": "Amazon Ember",
"color": "${viewport.theme == 'dark' ? 'white' : 'black' }"
},
{
"when": "${state.karaoke}",
"color": "blue"
}
]
},
"title": {
"extends": "baseText",
"values": [
{
"fontWeight": 700,
"fontSize": "${viewport.height > 400 ? 30 : 25}"
}
]
}
}
For example, the title
style on a device with a large viewport, dark theme, and focused state evaluates to:
{
"fontFamily": "Amazon Ember Display",
"color": "blue",
"fontWeight": 700,
"fontSize": 30
}
Not all component properties can be set with styles. See Styled Properties for a reference to all styled properties across all components. For an individual component, note the table of available component properties. Properties that you can set in a style for that component are indicated with "Yes" in the "Styled" column of the table. For example, see the Text properties table and note that all properties except
text
are styled.Definition properties
Each style definition has the following properties.
Property | Type | Required | Description |
---|---|---|---|
description |
String | No | A description of this style |
extend , extends |
Array of style names | No | List the styles that this style inherits from. Order is important; later items override earlier. |
value , values |
Array of VALUE objects | No | An array of objects |
The extend
array contains an ordered list of styles that this style inherits from. The access
property controls whether or not this style is available outside of the defining package.
The values
array contains an ordered list of value objects to apply. Each value
object has the form:
{
"when": EXPRESSION,
NAME: VALUE,
NAME: VALUE,
:
}
The when
property is optional and defaults to true
when not defined. The defined properties must be the names of valid styled properties. Invalid property names are ignored.. The data-binding context for the when
clause contains the viewport, environment, resource definitions, and styles that appear earlier in the document or in imported packages.
Component state
Each component has a state
. A state is a collection of defined Boolean properties that can change as the user interacts with the APL document. The following table lists all state properties:
Property | Definition |
---|---|
checked |
The component has been checked or toggled on |
disabled |
Component has been disabled (many components have a "disabled" property) |
focused |
Component has the keyboard input focus |
karaoke |
The component is being spoken by a speech command |
karaokeTarget |
An element within the component is the specific target for a current speech command. |
pressed |
Mouse or touch input is active |
hover |
The component has a cursor (mouse pointer) over the active input region. |
Each component state is a Boolean property. All component states are applicable to all components. For example, a Container
has a checked
and disabled
state even though those states might not have any visual effect on the Container
.
When a style is evaluated, the component states are exposed in the data-binding context as a map of Boolean values under the state
keyword. For example, you can access the checked
state as ${state.checked}
.
The component states are set during component creation and might change based on user interaction, such as by touching the screen or using a keyboard, or with commands. For example, a touchable component such as a TouchWrapper responds to touch or keyboard events by setting the pressed
state. The combination of component states and styles changes the visual appearance of a component.
Changing the state of a component doesn't change the state of the children in a component unless you set the inheritParentState
property. The following example shows a Text component that changes color when the user presses the component. The Text
component is wrapped in TouchWrapper
and is set to inherit the state of the TouchWrapper
:
Definition of a style:
{
"textStylePressable": {
"values": [
{
"color": "white"
},
{
"when": "${state.pressed}",
"color": "green"
}
]
}
}
Use of the style in a layout:
{
"type": "TouchWrapper",
"item": {
"type": "Text",
"inheritParentState": true,
"style": "textStylePressable",
"text": "Push Me!"
}
}
The following example shows the textStylePressable
. The text color changes to green when you press and hold the "Push Me" text.
Note the following rules around component state:
- Use the
SetValue
command to change to change thechecked
anddisabled
states. - A
disabled
component can't have thehover
,pressed
, orfocused
states set totrue
. Setting thedisabled
state totrue
on a component automatically sets thehover
,pressed
, andfocused
states tofalse
.
Component State Control
The following table summarizes state values and changes:
Concept | checked |
disabled |
focused |
karaoke |
karaokeTarget |
pressed |
hover |
---|---|---|---|---|---|---|---|
Initialized by property | Yes | Yes | -- | -- | -- | -- | -- |
Controlled by SetValue | Yes | Yes | -- | -- | -- | -- | -- |
Controlled by SetFocus | -- | -- | Yes | -- | -- | -- | -- |
Controlled by ClearFocus | -- | -- | Yes | -- | -- | -- | -- |
Changed by touch | -- | -- | -- | -- | -- | Yes | -- |
Changed by keyboard events | -- | -- | Yes | -- | -- | Yes | -- |
Changed by cursor enter/exit events | -- | -- | -- | -- | -- | -- | Yes |
Controlled by SpeakItem/List | -- | -- | -- | Yes | Yes | -- | -- |
Appears in event.source.value | Yes | -- | -- | -- | -- | -- | -- |
Appears in visual context | Yes | Yes | Yes | -- | -- | -- | -- |
The following example shows the checked
, disabled
, and pressed
states.
The following sections detail state behavior.
checked
Components use the checked
state to behave like a check box or radio button. You can control the checked
state for a component. It has the following characteristics:
- You can initialize the
checked
state in the component definition. - You can change
checked
dynamically with theSetValue
command. - The visual context reports the checked state.
When pressed, the TouchWrapper
component reports the checked
state of the component in the event.source.value
property. Use the checked
state to toggle check boxes as shown in the following example.
{
"onPress": {
"type": "SetValue",
"property": "checked",
"value": "${!event.source.checked}"
}
}
Touching a component on the screen doesn't automatically set or clear the checked state. You must change the state with the SetValue
command.
disabled
The disabled
state indicates that a component which normally responds to touch or keyboard events isn't available.
- You can initialize the
disabled
state in the component definition. - You can change the
disabled
state dynamically with theSetValue
command. - The visual context reports the disabled state.
A disabled component can't receive focus, doesn't respond to cursor change events, and can't be pressed. Setting the disabled state to true
on a component sets the hover, focused and pressed states to false
.
A disabled component can't receive focus and can't be pressed. Setting the disabled
state on a component to true
clears the pressed
and focused
states.
Disabling a component that contains children has no effect on the children of the disabled component unless they inherit their parent's state. For example, an enabled TouchWrapper
placed inside of a disabled Container
displays normally and responds to touch events.
focused
The focused
state reflects if the component currently has active focus in the runtime. The focused
state only applies to actionable components which can be controlled by a keyboard. The actionable components are: TouchWrapper
, Pager
, ScrollView
, and Sequence
.
- The focused state defaults to
false
when the component is initialized. - You can change the
focused
dynamically with theSetFocus
andClearFocus
commands. - The visual context reports the focused state.
The focused
state is controlled only by user navigation with keyboard
events, OR through use of the SetFocus
and ClearFocus
commands. The SetValue
command can't change the focused
state.
One parent component at a time has focused
state set to true. Child components with the inheritParentState
property set are assigned the focused
state for visual display, but don't receive keyboard events.
See the actionable component onFocus
and onBlur
properties for details on the event handlers associated with changes in the focused
state.
karaoke/karaokeTarget
The karaoke
state highlights a component when Alexa speaks or describes that component. The karaokeTarget
state highlights a subsection of the component being spoken or described by Alexa.
The karaoke state is controlled by the SpeakList
and SpeakItem
commands. The SetValue
command can't change the karaoke state.
For more about how to define karaoke styling, see Karaoke style calculations.
pressed
The pressed
state highlights a component when the user touches it or presses the "enter" key on a keyboard. The pressed
state applies to touchable components.
The TouchWrapper
component sets the pressed state when the user touches the component and releases the pressed state when the user either releases the touch or drags outside of the component bounds. The pressed state is re-entered if the user drags out and then drags back in.
The onPress
event handler for TouchWrapper
only fires if the user presses and releases in the same component. The onPress
command fires after the pressed
state has been released.
hover
The hover
state reflects if the component currently has a cursor (mouse pointer) over its active region. This state is only relevant on devices that have a cursor (mouse pointer) input controller. The hover state is set with the following algorithm. The system finds the top component under the cursor. Components that have the "display" property set to anything other than "visible" are ignored. If that component is disabled, no hover state is set. If that component has "inheritParentState" true, the first ancestor of that component with "inheritParentState" false has the hover state set. Otherwise, the component has the hover state set.
Not all devices have a cursor. On a device without a cursor, the hover
state of all components is fixed to false.
Disabled components don't respond to changes in cursor events or change hover states, but they do consume the cursor behavior over their active region as if they were enabled. A disabled child component of an enabled parent doesn't pass-through the cursor position within its active region to its parent, but rather consumes all cursor activity over its active region. See the component display
property for a more detailed breakdown of disabled component pass-through behavior.
See onCursorEnter and onCursorExit for details on the event handler associated with changes in the hover
state.
Evaluation of a style
Each component references a named style either explicitly or implicitly (device runtimes have default styles for each component). The style evaluation occurs in a restricted data-binding context with just the viewport
,state
, and resource
definitions.
The calculated style is a function of the viewport
, resources
, and state
. The calculation algorithm can be approximated with this pseudo-code:
function _calcInternal(style, context):
result = {};
// Walk the extend array
foreach style.extend as name:
result += _calcInternal( getStyleByName(name), context)
// Walk the values array
foreach values as value:
if evaluate(value.when):
result += evaluateEach(value)
return result
function calculateStyle(style, context, state):
workingContext = extendContext(context, { state: state })
return _calcInternal(style, workingContext)
The extend
array is calculated first, followed by the values
blocks (in order).
Karaoke style calculations
The karaoke
state property is set by the commands SpeakItem
and SpeakList
commands. In most cases the karaoke
state behaves just like any other state where all styled properties can be modified freely. There are two points for karaoke mode that should be kept in mind.
First, the SpeakItem
and SpeakList
commands scroll content into view before karaoke styling is applied. If the karaoke styling makes dramatic visual changes to the screen, the content might move unexpectedly after the scrolling has finished. Therefore, minimize changes that alter the position of components on the screen. This includes changing the size of components, text, fonts, and borders.
Second, the SpeakItem
property might be set to "highlight" individual lines of text during karaoke. When this highlighting mode is selected, the block of text is assigned the karaoke
state and the active line of text being spoken is assigned both the karaoke
state and the karaokeTarget
state. The karaokeTarget
state can only change the color of the highlighted text.
For example:
{
"styles": {
"karaokeText": {
"values": [
{
"color": "#fafafa",
"fontWeight": 300
},
{
"when": "${state.karaoke}",
"color": "blue",
"fontWeight": 700
},
{
"when": "${state.karaokeTarget}",
"color": "white",
"fontWeight": 100
}
]
}
}
}
In this example, the normal text color is #FAFAFA
(a light grey) with a fontWeight
of 300. During a SpeakItem
command with line-by-line highlighting, the fontWeight
of the entire text block is 700. The highlighted line is white (it has karaokeTarget=True
); the other lines are blue. Note that the fontWeight
setting for the karaoke target line was ignored because APL supports changing the color of the highlighted line only.
Line-by-line highlighting doesn't mean that there is always at least one visible highlighted line of text. If the minimumDwellTime
on the SpeakList
command property is set to longer than the speech duration, there might be a period where no lines are highlighted but the component still has the karaoke state set. Following the earlier example, during this "extra" time all of the lines of text are blue with a fontWeight
of 700.
Finally, if block highlighting mode is selected instead of line-by-line highlighting, all of the text is blue. The karaokeTarget
state isn't activated in block highlighting mode.
Last updated: Nov 28, 2023