Explore in Dialogflow
Click Continue to import our Responses sample in Dialogflow. Then, follow the steps below to deploy and test the sample:
- Enter an agent name and create a new Dialogflow agent for the sample.
- After the agent is done importing, click Go to agent.
- From the main navigation menu, go to Fulfillment.
- Enable the Inline Editor, then click Deploy. The editor contains the sample code.
- From the main navigation menu, go to Integrations, then click Google Assistant.
- In the modal window that appears, enable Auto-preview changes and click Test to open the Actions simulator.
- In the simulator, enter
Talk to my test app
to test the sample!
Use a visual selection response if you want the user to select a choice between several options in order to continue with your Action.
Visual selection responses can appear on screen-only experiences or on experiences that incorporate both audio and screen components.
Visual selection responses can contain the following components:
- One or two simple responses (chat bubbles).
- An optional basic card.
- Optional suggestion chips.
- An optional link-out chip.
- An option interface such as a list or carousel.
You can also review our conversation design guidelines to learn how to incorporate these visual elements in your Action.
Properties
Visual selection responses have the following requirements and optional properties that you can configure:
- Supported on surfaces with the
actions.capability.SCREEN_OUTPUT
capability. - The first item in a visual selection response must be a simple response.
- At most one simple response.
- At most one basic card, option interface (list or carousel),
or
StructuredResponse
. (You cannot have both a basic card and an option interface at the same time). - At most 8 suggestion chips.
- Suggestion chips are not allowed in a
FinalResponse
.
The following sections show you how to build various types of visual selection responses.
List
The single-select list presents the user with a vertical list of multiple items and allows the user to select a single one. Selecting an item from the list generates a user query (chat bubble) containing the title of the list item.
The list response type is supported on surfaces with the
actions.capability.SCREEN_OUTPUT
capability.
Properties
Lists must contain a minimum of 2 and a maximum of 30 list items. Lists have the following properties:
- List title (optional)
- Fixed font and font size
- Restricted to a single line. (Excessive characters are truncated.)
- Plain text, Markdown is not supported.
- The card height collapses if no title is specified.
- List item
- Title
- Fixed font and font size
- Max length: 1 line (truncated with ellipses…)
- Required to be unique (to support voice selection)
- Description (optional)
- Fixed font and font size
- Max length: 2 lines (truncated with ellipses…)
- Image (optional)
- Size: 48x48 px
- Title
- Interaction
- Voice/Text
- The user can always say or type an item's title instead of tapping it.
- Must have an intent for touch input that handles the
actions_intent_OPTION
event.
- Voice/Text
Guidance
Lists are good for when it's important to disambiguate options, or when the user needs to choose between options that need to be scanned at a glance. For example, which "Peter" do you need to speak to, Peter Jons or Peter Hans?
We recommend adding suggestion chips below a list to enable the user to pivot or expand the conversation. Never repeat the options presented in the list as suggestion chips. Chips in this context are use to pivot the conversation (not for choice selection).
Notice that in the accompanying example, the chat bubble that accompanies the list card is a subset of the audio (TTS/SSML). The audio output includes only the first list item. We discourage reading all the elements from the list.
Make sure your Action shows what is most important to your users at the top of the list (for example, the most popular, the recently purchased, or the most talked about). The list initially displays up to 10 elements, but users can expand the list to show more elements. The number of items that the list shows before expansion may also change depending on the surface and time.
Sample code
Node.js
app.intent('List', (conv) => { if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); return; } conv.ask('This is a list example.'); // Create a list conv.ask(new List({ title: 'List Title', items: { // Add the first item to the list 'SELECTION_KEY_ONE': { synonyms: [ 'synonym 1', 'synonym 2', 'synonym 3', ], title: 'Title of First List Item', description: 'This is a description of a list item.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), }, // Add the second item to the list 'SELECTION_KEY_GOOGLE_HOME': { synonyms: [ 'Google Home Assistant', 'Assistant on the Google Home', ], title: 'Google Home', description: 'Google Home is a voice-activated speaker powered by ' + 'the Google Assistant.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Home', }), }, // Add the third item to the list 'SELECTION_KEY_GOOGLE_PIXEL': { synonyms: [ 'Google Pixel XL', 'Pixel', 'Pixel XL', ], title: 'Google Pixel', description: 'Pixel. Phone by Google.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Pixel', }), }, }, })); });
Java
@ForIntent("List") public ActionResponse list(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a list example.") .add( new SelectionList() .setTitle("List Title") .setItems( Arrays.asList( new ListSelectListItem() .setTitle("Title of First List Item") .setDescription("This is a description of a list item.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("synonym 1", "synonym 2", "synonym 3")) .setKey("SELECTION_KEY_ONE")), new ListSelectListItem() .setTitle("Google Home") .setDescription( "Google Home is a voice-activated speaker powered by the Google Assistant.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Home")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList( "Google Home Assistant", "Assistant on the Google Home")) .setKey("SELECTION_KEY_GOOGLE_HOME")), new ListSelectListItem() .setTitle("Google Pixel") .setDescription("Pixel. Phone by Google.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Pixel")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("Google Pixel XL", "Pixel", "Pixel XL")) .setKey("SELECTION_KEY_GOOGLE_PIXEL"))))); return responseBuilder.build(); }
Node.js
if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); return; } conv.ask('This is a list example.'); // Create a list conv.ask(new List({ title: 'List Title', items: { // Add the first item to the list 'SELECTION_KEY_ONE': { synonyms: [ 'synonym 1', 'synonym 2', 'synonym 3', ], title: 'Title of First List Item', description: 'This is a description of a list item.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), }, // Add the second item to the list 'SELECTION_KEY_GOOGLE_HOME': { synonyms: [ 'Google Home Assistant', 'Assistant on the Google Home', ], title: 'Google Home', description: 'Google Home is a voice-activated speaker powered by ' + 'the Google Assistant.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Home', }), }, // Add the third item to the list 'SELECTION_KEY_GOOGLE_PIXEL': { synonyms: [ 'Google Pixel XL', 'Pixel', 'Pixel XL', ], title: 'Google Pixel', description: 'Pixel. Phone by Google.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Pixel', }), }, }, }));
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a list example.") .add( new SelectionList() .setTitle("List Title") .setItems( Arrays.asList( new ListSelectListItem() .setTitle("Title of First List Item") .setDescription("This is a description of a list item.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("synonym 1", "synonym 2", "synonym 3")) .setKey("SELECTION_KEY_ONE")), new ListSelectListItem() .setTitle("Google Home") .setDescription( "Google Home is a voice-activated speaker powered by the Google Assistant.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Home")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList( "Google Home Assistant", "Assistant on the Google Home")) .setKey("SELECTION_KEY_GOOGLE_HOME")), new ListSelectListItem() .setTitle("Google Pixel") .setDescription("Pixel. Phone by Google.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Pixel")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("Google Pixel XL", "Pixel", "Pixel XL")) .setKey("SELECTION_KEY_GOOGLE_PIXEL"))))); return responseBuilder.build();
JSON
Note that the JSON below describes a webhook response.
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.OPTION", "data": { "@type": "type.googleapis.com/google.actions.v2.OptionValueSpec", "listSelect": { "title": "List Title", "items": [ { "optionInfo": { "key": "SELECTION_KEY_ONE", "synonyms": [ "synonym 1", "synonym 2", "synonym 3" ] }, "description": "This is a description of a list item.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" }, "title": "Title of First List Item" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_HOME", "synonyms": [ "Google Home Assistant", "Assistant on the Google Home" ] }, "description": "Google Home is a voice-activated speaker powered by the Google Assistant.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Home" }, "title": "Google Home" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_PIXEL", "synonyms": [ "Google Pixel XL", "Pixel", "Pixel XL" ] }, "description": "Pixel. Phone by Google.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Pixel" }, "title": "Google Pixel" } ] } } }, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "This is a list example." } } ] } } } }
JSON
Note that the JSON below describes a webhook response.
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.OPTION", "inputValueData": { "@type": "type.googleapis.com/google.actions.v2.OptionValueSpec", "listSelect": { "title": "List Title", "items": [ { "optionInfo": { "key": "SELECTION_KEY_ONE", "synonyms": [ "synonym 1", "synonym 2", "synonym 3" ] }, "description": "This is a description of a list item.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" }, "title": "Title of First List Item" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_HOME", "synonyms": [ "Google Home Assistant", "Assistant on the Google Home" ] }, "description": "Google Home is a voice-activated speaker powered by the Google Assistant.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Home" }, "title": "Google Home" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_PIXEL", "synonyms": [ "Google Pixel XL", "Pixel", "Pixel XL" ] }, "description": "Pixel. Phone by Google.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Pixel" }, "title": "Google Pixel" } ] } } } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "This is a list example." } } ] } } } ] }
Handling a selected item
When users select an item, the selected item value is passed to you as an
argument. In the argument value, you will get the key
identifier for the
selected item:
Node.js
app.intent('List - OPTION', (conv, params, option) => { const SELECTED_ITEM_RESPONSES = { 'SELECTION_KEY_ONE': 'You selected the first item', 'SELECTION_KEY_GOOGLE_HOME': 'You selected the Google Home!', 'SELECTION_KEY_GOOGLE_PIXEL': 'You selected the Google Pixel!', }; conv.ask(SELECTED_ITEM_RESPONSES[option]); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("List - OPTION") public ActionResponse listSelected(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); String selectedItem = request.getSelectedOption(); String response; if (selectedItem.equals("SELECTION_KEY_ONE")) { response = "You selected the first item"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_HOME")) { response = "You selected the Google Home!"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_PIXEL")) { response = "You selected the Google Pixel!"; } else { response = "You did not select a valid item"; } return responseBuilder.add(response).add("Which response would you like to see next?").build(); }
Node.js
app.intent('actions.intent.OPTION', (conv, params, option) => { const SELECTED_ITEM_RESPONSES = { 'SELECTION_KEY_ONE': 'You selected the first item', 'SELECTION_KEY_GOOGLE_HOME': 'You selected the Google Home!', 'SELECTION_KEY_GOOGLE_PIXEL': 'You selected the Google Pixel!', }; conv.ask(SELECTED_ITEM_RESPONSES[option]); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("actions.intent.OPTION") public ActionResponse listSelected(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); String selectedItem = request.getSelectedOption(); String response; if (selectedItem.equals("SELECTION_KEY_ONE")) { response = "You selected the first item"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_HOME")) { response = "You selected the Google Home!"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_PIXEL")) { response = "You selected the Google Pixel!"; } else { response = "You did not select a valid item"; } return responseBuilder.add(response).add("Which response would you like to see next?").build(); } public ActionResponse carousel(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a carousel example.") .add( new SelectionCarousel() .setItems( Arrays.asList( new CarouselSelectCarouselItem() .setTitle("Title of First List Item") .setDescription("This is a description of a list item.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("synonym 1", "synonym 2", "synonym 3")) .setKey("SELECTION_KEY_ONE")), new CarouselSelectCarouselItem() .setTitle("Google Home") .setDescription( "Google Home is a voice-activated speaker powered by the Google Assistant.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Home")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList( "Google Home Assistant", "Assistant on the Google Home")) .setKey("SELECTION_KEY_GOOGLE_HOME")), new CarouselSelectCarouselItem() .setTitle("Google Pixel") .setDescription("Pixel. Phone by Google.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Pixel")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("Google Pixel XL", "Pixel", "Pixel XL")) .setKey("SELECTION_KEY_GOOGLE_PIXEL"))))); return responseBuilder.build(); } }
JSON
Note that the JSON below describes a webhook request.
{ "responseId": "5d7732d1-d22d-4a0e-ad34-8bc0a7fde20c-21947381", "queryResult": { "queryText": "actions_intent_OPTION", "action": "List.List-custom", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "Webhook failed for intent: List - OPTION", "fulfillmentMessages": [ { "text": { "text": [ "Webhook failed for intent: List - OPTION" ] } } ], "outputContexts": [ { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_screen_output" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_account_linking" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_media_response_audio" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_audio_output" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_web_browser" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/google_assistant_input_type_touch" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/list-followup", "lifespanCount": 1 }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_intent_option", "parameters": { "OPTION": "SELECTION_KEY_GOOGLE_PIXEL", "text": "Google Pixel" } } ], "intent": { "name": "projects/df-responses-kohler/agent/intents/88904350-193e-4472-a2de-977eb5d9e26e", "displayName": "List - OPTION" }, "intentDetectionConfidence": 1, "languageCode": "en" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "user": { "locale": "en-US", "lastSeen": "2019-08-04T23:56:32Z", "userVerificationStatus": "VERIFIED" }, "conversation": { "conversationId": "ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA", "type": "ACTIVE", "conversationToken": "[\"list-followup\"]" }, "inputs": [ { "intent": "actions.intent.OPTION", "rawInputs": [ { "inputType": "TOUCH", "query": "Google Pixel" } ], "arguments": [ { "name": "OPTION", "textValue": "SELECTION_KEY_GOOGLE_PIXEL" }, { "name": "text", "rawText": "Google Pixel", "textValue": "Google Pixel" } ] } ], "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.ACCOUNT_LINKING" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "isInSandbox": true, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" } ] } ], "requestType": "SIMULATOR" } }, "session": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA" }
JSON
Note that the JSON below describes a webhook request.
{ "user": { "locale": "en-US", "lastSeen": "2019-08-06T07:37:53Z", "userVerificationStatus": "VERIFIED" }, "conversation": { "conversationId": "ABwppHGcqunXh1M6IE0lu2sVqXdpJfdpC5FWMkMSXQskK1nzb4IkSUSRqQzoEr0Ly0z_G3mwyZlk5rFtd1w", "type": "NEW" }, "inputs": [ { "intent": "actions.intent.OPTION", "rawInputs": [ { "inputType": "TOUCH", "query": "Google Home" } ], "arguments": [ { "name": "OPTION", "textValue": "SELECTION_KEY_GOOGLE_HOME" }, { "name": "text", "rawText": "Google Home", "textValue": "Google Home" } ] } ], "surface": { "capabilities": [ { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.ACCOUNT_LINKING" }, { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "isInSandbox": true, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.SCREEN_OUTPUT" } ] } ], "requestType": "SIMULATOR" }
Carousel
The carousel scrolls horizontally and allows for selecting one item. Compared to the list selector, it has large tiles-allowing for richer content. The tiles that make up a carousel are similar to the basic card with image. Selecting an item from the carousel generates a chat bubble as the response just like with list selector.
Properties
The carousel response type has the following requirements and optional properties that you can configure:
- Supported on surfaces with the
actions.capability.SCREEN_OUTPUT
capability. - Carousel
- Maximum of ten tiles.
- Minimum of two tiles.
- Plain text, Markdown is not supported.
- Carousel tile
- Image (optional)
- Image is forced to be 128 dp tall x 232 dp wide
- If the image aspect ratio doesn't match the image bounding box, then the image is centered with bars on either side
- If an image link is broken then a placeholder image is used instead
- Title (required)
- Same as the Basic Text Card
- Titles must be unique (to support voice selection)
- Description (optional)
- Same formatting options as the Basic Text Card
- Max 4 lines
- Plain text, Markdown is not supported.
- Image (optional)
- Interaction
- Swipe left/right: Slide the carousel to reveal different cards.
- Tap card: Tapping an item simply generates a chat bubble with the same
text as the element title.
- Must have an intent for touch input that handles the
actions_intent_OPTION
event.
- Must have an intent for touch input that handles the
- Voice/Keyboard: Replying with the card title (if specified) functions the same as selecting that item.
Guidance
Carousels are good when various options are presented to the user, but a direct comparison is not required among them (versus lists). In general, prefer lists to carousels because lists are easier to visually scan and to interact with by voice.
If you want to build a carousel with items that link out to web pages, you may want to build a browsing carousel instead.
We recommend adding suggestion chips below a carousel if you want to continue the conversation.
Never repeat the options presented in the list as suggestion chips. Chips in this context are used to pivot the conversation (not for choice selection).
As with lists, the chat bubble that accompanies the carousel card is a subset of the audio (TTS/SSML). The audio (TTS/SSML) here integrates the first tile in the carousel, and we also strongly discourage reading all the elements from the carousel. It's best to mention the first item and the reason why it's there (for example, the most popular, the most recently purchased, or the most talked about).
Sample code
Node.js
app.intent('Carousel', (conv) => { if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); return; } conv.ask('This is a carousel example.'); // Create a carousel conv.ask(new Carousel({ title: 'Carousel Title', items: { // Add the first item to the carousel 'SELECTION_KEY_ONE': { synonyms: [ 'synonym 1', 'synonym 2', 'synonym 3', ], title: 'Title of First Carousel Item', description: 'This is a description of a carousel item.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), }, // Add the second item to the carousel 'SELECTION_KEY_GOOGLE_HOME': { synonyms: [ 'Google Home Assistant', 'Assistant on the Google Home', ], title: 'Google Home', description: 'Google Home is a voice-activated speaker powered by ' + 'the Google Assistant.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Home', }), }, // Add the third item to the carousel 'SELECTION_KEY_GOOGLE_PIXEL': { synonyms: [ 'Google Pixel XL', 'Pixel', 'Pixel XL', ], title: 'Google Pixel', description: 'Pixel. Phone by Google.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Pixel', }), }, }, })); });
Java
@ForIntent("Carousel") public ActionResponse carousel(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a carousel example.") .add( new SelectionCarousel() .setItems( Arrays.asList( new CarouselSelectCarouselItem() .setTitle("Title of First List Item") .setDescription("This is a description of a list item.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("synonym 1", "synonym 2", "synonym 3")) .setKey("SELECTION_KEY_ONE")), new CarouselSelectCarouselItem() .setTitle("Google Home") .setDescription( "Google Home is a voice-activated speaker powered by the Google Assistant.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Home")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList( "Google Home Assistant", "Assistant on the Google Home")) .setKey("SELECTION_KEY_GOOGLE_HOME")), new CarouselSelectCarouselItem() .setTitle("Google Pixel") .setDescription("Pixel. Phone by Google.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Pixel")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("Google Pixel XL", "Pixel", "Pixel XL")) .setKey("SELECTION_KEY_GOOGLE_PIXEL"))))); return responseBuilder.build(); }
Node.js
if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); return; } conv.ask('This is a carousel example.'); // Create a carousel conv.ask(new Carousel({ title: 'Carousel Title', items: { // Add the first item to the carousel 'SELECTION_KEY_ONE': { synonyms: [ 'synonym 1', 'synonym 2', 'synonym 3', ], title: 'Title of First Carousel Item', description: 'This is a description of a carousel item.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), }, // Add the second item to the carousel 'SELECTION_KEY_GOOGLE_HOME': { synonyms: [ 'Google Home Assistant', 'Assistant on the Google Home', ], title: 'Google Home', description: 'Google Home is a voice-activated speaker powered by ' + 'the Google Assistant.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Home', }), }, // Add the third item to the carousel 'SELECTION_KEY_GOOGLE_PIXEL': { synonyms: [ 'Google Pixel XL', 'Pixel', 'Pixel XL', ], title: 'Google Pixel', description: 'Pixel. Phone by Google.', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Google Pixel', }), }, }, }));
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a carousel example.") .add( new SelectionCarousel() .setItems( Arrays.asList( new CarouselSelectCarouselItem() .setTitle("Title of First List Item") .setDescription("This is a description of a list item.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("synonym 1", "synonym 2", "synonym 3")) .setKey("SELECTION_KEY_ONE")), new CarouselSelectCarouselItem() .setTitle("Google Home") .setDescription( "Google Home is a voice-activated speaker powered by the Google Assistant.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Home")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList( "Google Home Assistant", "Assistant on the Google Home")) .setKey("SELECTION_KEY_GOOGLE_HOME")), new CarouselSelectCarouselItem() .setTitle("Google Pixel") .setDescription("Pixel. Phone by Google.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Pixel")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("Google Pixel XL", "Pixel", "Pixel XL")) .setKey("SELECTION_KEY_GOOGLE_PIXEL"))))); return responseBuilder.build();
JSON
Note that the JSON below describes a webhook request.
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.OPTION", "data": { "@type": "type.googleapis.com/google.actions.v2.OptionValueSpec", "carouselSelect": { "items": [ { "optionInfo": { "key": "SELECTION_KEY_ONE", "synonyms": [ "synonym 1", "synonym 2", "synonym 3" ] }, "description": "This is a description of a carousel item.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" }, "title": "Title of First Carousel Item" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_HOME", "synonyms": [ "Google Home Assistant", "Assistant on the Google Home" ] }, "description": "Google Home is a voice-activated speaker powered by the Google Assistant.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Home" }, "title": "Google Home" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_PIXEL", "synonyms": [ "Google Pixel XL", "Pixel", "Pixel XL" ] }, "description": "Pixel. Phone by Google.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Pixel" }, "title": "Google Pixel" } ] } } }, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "This is a carousel example." } } ] } } } }
JSON
Note that the JSON below describes a webhook request.
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.OPTION", "inputValueData": { "@type": "type.googleapis.com/google.actions.v2.OptionValueSpec", "carouselSelect": { "items": [ { "optionInfo": { "key": "SELECTION_KEY_ONE", "synonyms": [ "synonym 1", "synonym 2", "synonym 3" ] }, "description": "This is a description of a carousel item.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" }, "title": "Title of First Carousel Item" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_HOME", "synonyms": [ "Google Home Assistant", "Assistant on the Google Home" ] }, "description": "Google Home is a voice-activated speaker powered by the Google Assistant.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Home" }, "title": "Google Home" }, { "optionInfo": { "key": "SELECTION_KEY_GOOGLE_PIXEL", "synonyms": [ "Google Pixel XL", "Pixel", "Pixel XL" ] }, "description": "Pixel. Phone by Google.", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Google Pixel" }, "title": "Google Pixel" } ] } } } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "This is a carousel example." } } ] } } } ] }
Handling selected item
When users select an item, the selected item value is passed to you as an
argument. In the argument value, you will get the key
identifier for the
selected item:
Node.js
app.intent('Carousel - OPTION', (conv, params, option) => { const SELECTED_ITEM_RESPONSES = { 'SELECTION_KEY_ONE': 'You selected the first item', 'SELECTION_KEY_GOOGLE_HOME': 'You selected the Google Home!', 'SELECTION_KEY_GOOGLE_PIXEL': 'You selected the Google Pixel!', }; conv.ask(SELECTED_ITEM_RESPONSES[option]); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("Carousel - OPTION") public ActionResponse carouselSelected(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); String selectedItem = request.getSelectedOption(); String response; if (selectedItem.equals("SELECTION_KEY_ONE")) { response = "You selected the first item"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_HOME")) { response = "You selected the Google Home!"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_PIXEL")) { response = "You selected the Google Pixel!"; } else { response = "You did not select a valid item"; } return responseBuilder.add(response).add("Which response would you like to see next?").build(); }
Node.js
app.intent('actions.intent.OPTION', (conv, params, option) => { const SELECTED_ITEM_RESPONSES = { 'SELECTION_KEY_ONE': 'You selected the first item', 'SELECTION_KEY_GOOGLE_HOME': 'You selected the Google Home!', 'SELECTION_KEY_GOOGLE_PIXEL': 'You selected the Google Pixel!', }; conv.ask(SELECTED_ITEM_RESPONSES[option]); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("actions.intent.OPTION") public ActionResponse listSelected(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); String selectedItem = request.getSelectedOption(); String response; if (selectedItem.equals("SELECTION_KEY_ONE")) { response = "You selected the first item"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_HOME")) { response = "You selected the Google Home!"; } else if (selectedItem.equals("SELECTION_KEY_GOOGLE_PIXEL")) { response = "You selected the Google Pixel!"; } else { response = "You did not select a valid item"; } return responseBuilder.add(response).add("Which response would you like to see next?").build(); } public ActionResponse carousel(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a carousel example.") .add( new SelectionCarousel() .setItems( Arrays.asList( new CarouselSelectCarouselItem() .setTitle("Title of First List Item") .setDescription("This is a description of a list item.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("synonym 1", "synonym 2", "synonym 3")) .setKey("SELECTION_KEY_ONE")), new CarouselSelectCarouselItem() .setTitle("Google Home") .setDescription( "Google Home is a voice-activated speaker powered by the Google Assistant.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Home")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList( "Google Home Assistant", "Assistant on the Google Home")) .setKey("SELECTION_KEY_GOOGLE_HOME")), new CarouselSelectCarouselItem() .setTitle("Google Pixel") .setDescription("Pixel. Phone by Google.") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Google Pixel")) .setOptionInfo( new OptionInfo() .setSynonyms( Arrays.asList("Google Pixel XL", "Pixel", "Pixel XL")) .setKey("SELECTION_KEY_GOOGLE_PIXEL"))))); return responseBuilder.build(); } }
JSON
Note that the JSON below describes a webhook request.
{ "responseId": "fd9c865a-e628-4e89-ae72-14a002361244-21947381", "queryResult": { "queryText": "actions_intent_OPTION", "action": "Carousel.Carousel-custom", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "Webhook failed for intent: Carousel - OPTION", "fulfillmentMessages": [ { "text": { "text": [ "Webhook failed for intent: Carousel - OPTION" ] } } ], "outputContexts": [ { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_media_response_audio" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_account_linking" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_web_browser" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_screen_output" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_audio_output" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/google_assistant_input_type_touch" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/carousel-followup", "lifespanCount": 1 }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_intent_option", "parameters": { "OPTION": "SELECTION_KEY_ONE", "text": "Title of First Carousel Item" } } ], "intent": { "name": "projects/df-responses-kohler/agent/intents/89289810-95e0-4dfd-a26a-b49a2ac51406", "displayName": "Carousel - OPTION" }, "intentDetectionConfidence": 1, "languageCode": "en" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "user": { "locale": "en-US", "lastSeen": "2019-08-04T23:59:37Z", "userVerificationStatus": "VERIFIED" }, "conversation": { "conversationId": "ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA", "type": "ACTIVE", "conversationToken": "[\"carousel-followup\"]" }, "inputs": [ { "intent": "actions.intent.OPTION", "rawInputs": [ { "inputType": "TOUCH", "query": "Title of First Carousel Item" } ], "arguments": [ { "name": "OPTION", "textValue": "SELECTION_KEY_ONE" }, { "name": "text", "rawText": "Title of First Carousel Item", "textValue": "Title of First Carousel Item" } ] } ], "surface": { "capabilities": [ { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.ACCOUNT_LINKING" }, { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" } ] }, "isInSandbox": true, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.SCREEN_OUTPUT" } ] } ], "requestType": "SIMULATOR" } }, "session": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA" }
JSON
Note that the JSON below describes a webhook request.
{ "user": { "locale": "en-US", "lastSeen": "2019-08-06T07:37:15Z", "userVerificationStatus": "VERIFIED" }, "conversation": { "conversationId": "ABwppHGcqunXh1M6IE0lu2sVqXdpJfdpC5FWMkMSXQskK1nzb4IkSUSRqQzoEr0Ly0z_G3mwyZlk5rFtd1w", "type": "NEW" }, "inputs": [ { "intent": "actions.intent.OPTION", "rawInputs": [ { "inputType": "TOUCH", "query": "Google Home" } ], "arguments": [ { "name": "OPTION", "textValue": "SELECTION_KEY_GOOGLE_HOME" }, { "name": "text", "rawText": "Google Home", "textValue": "Google Home" } ] } ], "surface": { "capabilities": [ { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.ACCOUNT_LINKING" } ] }, "isInSandbox": true, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.SCREEN_OUTPUT" } ] } ], "requestType": "SIMULATOR" }