Follow-up question
A follow-up question appears after I've answered another question in a specific way.
The Follow-up question component progressively discloses a clarifying question. It only appears if an initial question is answered in a specific way. It and the question preceding it are visible on the same page.
Details
- Honeycrisp: Follow-up question molecule
- Customization: Design tokens and Styles
- Modifier:
.cfa-follow-up-question .cfa-follow-up-question__region
The Follow-up question is a custom component. It includes the form group component, fieldset component, and any other components necessary to create a form. The visual appearance uses design tokens from the Honeycrisp Follow-up question molecule. Further customization is applied using the CSS modifiers .cfa-follow-up-question .cfa-follow-up-question__region
to add styles defined in a custom stylesheet.
Design tokens. Tokens define the name of basic system elements such as color, typography, or spacing. The values of tokens are relative to how the system defines them. This enables teams to alter the visual appearance of components yet remain within the system boundaries. Learn more about tokens on the Bixal's Design Tokens Guide.
Modifier. A modifier is a class name that applies a variant, type, or extended style customization to modify the component's visual appearance.
Examples
The HTML for the demonstration above is rendered using context passed to the component's Thymeleaf template fragment. Learn how to include component templates for Thymeleaf and Ruby in the source and usage section.
<div class="cfa-follow-up">
<div>
<fieldset class="usa-fieldset cfa-fieldset">
<legend class="usa-legend cfa-legend">
<span>Do you think you will make less from this job this year?</span>
</legend>
<div class="usa-radio cfa-radio">
<input type="radio" id="radio-7c9fa86fee5bf" name="radio['4386479c33c9e']" value="yes" data-js="follow-up-question" class="usa-radio__input usa-radio__input--tile">
<label for="radio-7c9fa86fee5bf" class="usa-radio__label">
<span>Yes</span>
</label>
</div>
<div class="usa-radio cfa-radio">
<input type="radio" id="radio-05febabe62d37" name="radio['4386479c33c9e']" value="no" data-js="follow-up-question" data-aria-controls="aria-c-6d38f63b7ce42" class="usa-radio__input usa-radio__input--tile">
<label for="radio-05febabe62d37" class="usa-radio__label">
<span>No</span>
</label>
</div>
</fieldset>
</div>
<div class="cfa-follow-up__region" id="aria-c-6d38f63b7ce42">
<div>
<div>
<div>
<div class="usa-form-group cfa-form-group" id="form-group-26505611dde1e">
<label class="usa-label cfa-label" for="input-26505611dde1e">
<span>What do you think you'll make this year?</span>
</label>
<div class="usa-hint cfa-hint" id="hint-26505611dde1e">We know this can be hard to answer. You can estimate or guess what you’ll make before taxes and deductions. We’ll use this to calculate and report your monthly pay.</div>
<div>
<div class="usa-input-group cfa-input-group">
<div class="usa-input-prefix" aria-hidden="true">$</div>
<input class="usa-input cfa-input text-end" type="text" id="input-26505611dde1e" name="input['26505611dde1e']" aria-describedby="hint-26505611dde1e" placeholder="0.00" inputmode="decimal" data-js="mask-dollars">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Context is the information necessary to configure and render a component template to HTML. It may include plain text strings, HTML, class names, IDs or other HTML attribute values. The context here is defined as JSON but the root attributes are translated to different variable syntaxes for Thymeleaf and Ruby templates. Learn how to pass these variables to each component template in the source and usage section.
{
"fieldset": {
"modifier": "cfa-fieldset",
"legend": {
"text": "Do you think you will make less from this job this year?",
"modifier": "cfa-legend",
"modifierHint": "cfa-hint"
},
"options": [
{
"modifier": "cfa-radio",
"id": "radio-7c9fa86fee5bf",
"name": "radio['4386479c33c9e']",
"type": "radio",
"value": "yes",
"label": "Yes",
"js": "follow-up-question",
"input": {
"modifier": "usa-radio__input--tile"
}
},
{
"modifier": "cfa-radio",
"id": "radio-05febabe62d37",
"name": "radio['4386479c33c9e']",
"type": "radio",
"value": "no",
"label": "No",
"js": "follow-up-question",
"dataAriaControls": "aria-c-6d38f63b7ce42",
"input": {
"modifier": "usa-radio__input--tile"
}
}
]
},
"region": {
"id": "aria-c-6d38f63b7ce42",
"formGroups": [
{
"modifier": "cfa-form-group",
"id": "form-group-26505611dde1e",
"label": {
"text": "What do you think you'll make this year?",
"for": "input-26505611dde1e",
"modifier": "cfa-label",
"modifierHint": "cfa-hint"
},
"hint": {
"text": "We know this can be hard to answer. You can estimate or guess what you’ll make before taxes and deductions. We’ll use this to calculate and report your monthly pay.",
"modifier": "cfa-hint",
"id": "hint-26505611dde1e"
},
"inputGroup": {
"modifier": "usa-input-group cfa-input-group"
},
"input": {
"modifier": "cfa-input text-end",
"id": "input-26505611dde1e",
"name": "input['26505611dde1e']",
"ariaDescribedby": "hint-26505611dde1e",
"type": "text",
"prefixText": "$",
"placeholder": "0.00",
"inputmode": "decimal",
"js": "mask-dollars"
}
}
]
}
}
Guidance
Usage. The Follow-up question component should be used sparingly to avoid the risk of users accidentally missing questions contained in the follow-up region. Alternatively, use a form card with a single question, yes, and no links instead.
ARIA attributes. The aria-expanded
attribute is not included in this component because it is currently an invalid attribute for radio elements.
Additional references
Accessibility
Checklist Key
Design library component
Source and usage
- Sass stylesheet:
./_cfa-follow-up-question.scss
- Thymeleaf template fragment:
./cfa-follow-up-question.th.html
- Embedded Ruby (ERB) partial template:
./_cfa-follow-up-question.html.erb
- JavaScript enables the expansion and collapse of the follow-up region. It also toggles the following attributes on child elements inside the follow-up region; tabindex on potentially focusable children and disabled attribute on form element children. The module can be found at
./cfa-follow-up-question.js
. Guidance on individual module loading will added. Currently, there is one script that imports all modules. The source is located at@codeforamerica/js/index.js
. This entrypoint is compiled using Rollup.js and distributed to@codeforamerica/dist/js/default.js
.
Below is a demonstration of customizing the component theme settings. Refer to the theme and package-level settings documentation.
// Theme-level settings
@use 'cfa-uswds-theme' with (
// Global theme settings that affect the component, changing these will affect other components
$cfa-color-base-lightest: 'gray-warm-4', // Affects follow-up question background
$cfa-color-base-darker: 'gray-warm-70' // Affects follow-up question container border
);
// Package-level settings
@use 'cfa-core' with (
// Global theme settings that affect the component, changing these will affect other components
$cfa-form-elements-border-width: 2px, // Affects follow-up question border
$cfa-input-select-width: 2 // Affects follow-up question arrow position
);
This is the pre-rendered template fragment from the package. It is the same template used to render the demonstrations above. You may copy and paste from this example or use the template using the th:block th:replace
tag. See the example below.
<div th:fragment="followUpQuestion(modifier, fieldset, region)" class="cfa-follow-up" th:classappend="${modifier}">
<div th:if="${fieldset}">
<th:block th:replace="~{packages/cfa-fieldset/cfa-fieldset.th :: fieldset(${fieldset.modifier}, ${fieldset.id}, ${fieldset.legend}, ${fieldset.hint}, ${fieldset.required}, ${fieldset.error}, ${fieldset.options})}" />
</div>
<div th:if="${region.formGroups}" class="cfa-follow-up__region" th:classappend="${region.modifier}" th:attr="id=${region.id},role=${region.role},aria-label=${region.ariaLabel}">
<div th:each="formGroup: ${region.formGroups}">
<div th:if="${formGroup.input}">
<div th:if="${formGroup.inputGroup}">
<th:block th:replace="~{packages/cfa-form-group/cfa-form-group.th :: formGroup(${formGroup.modifier}, ${formGroup.id}, ${formGroup.label}, ${formGroup.input}, '', '', ${formGroup.hint}, ${formGroup.error}, ${formGroup.inputGroup})}" />
</div>
<div th:unless="${formGroup.inputGroup}">
<th:block th:replace="~{packages/cfa-form-group/cfa-form-group.th :: formGroup(${formGroup.modifier}, ${formGroup.id}, ${formGroup.label}, ${formGroup.input}, '', '', ${formGroup.hint}, ${formGroup.error}, '')}" />
</div>
</div>
<div th:if="${formGroup.textarea}">
<th:block th:replace="~{packages/cfa-form-group/cfa-form-group.th :: formGroup(${formGroup.modifier}, ${formGroup.id}, ${formGroup.label}, '', '', ${formGroup.textarea}, ${formGroup.hint}, ${formGroup.error}, '')}" />
</div>
<div th:if="${formGroup.selectEl}">
<th:block th:replace="~{packages/cfa-form-group/cfa-form-group.th :: formGroup(${formGroup.modifier}, ${formGroup.id}, ${formGroup.label}, '', '', '', ${formGroup.hint}, ${formGroup.error}, ${formGroup.selectEl})}" />
</div>
<div th:if="${formGroup.memorableDate}">
<th:block th:replace="~{packages/cfa-memorable-date/cfa-memorable-date.th :: memorableDate(${formGroup.fieldset}, ${formGroup.id}, ${formGroup.required}, ${formGroup.modifier}, ${formGroup.month}, ${formGroup.day}, ${formGroup.year})}" />
</div>
<div th:if="${formGroup.options}">
<th:block th:replace="~{packages/cfa-fieldset/cfa-fieldset.th :: fieldset(${formGroup.modifier}, ${formGroup.id}, ${formGroup.legend}, ${formGroup.hint}, ${formGroup.required}, ${formGroup.error}, ${formGroup.options})}" />
</div>
</div>
</div>
</div>
Below is an example of how to use the fragment from the package directory using the th:block th:replace
inclusion tag. Replace the fragment parameters using your variables or context.
<th:block th:replace="~{packages/cfa-follow-up-question/cfa-follow-up-question.th :: followUpQuestion(${modifier}, ${fieldset}, ${region})}" />
This is the pre-rendered partial template from the package. You may copy and paste from this example or use the template directly from the path.
<div class="cfa-follow-up<% if defined?(modifier) %> <%= modifier %><% end %>">
<% if defined?(fieldset) %><div>
<%= ERB.new(File.read('packages/cfa-fieldset/_cfa-fieldset.html.erb'), 0, 0, [*('a'..'z')].sample(8).join)
.result_with_hash({
modifier: (fieldset['modifier'] if fieldset['modifier']),
id: (fieldset['id'] if fieldset['id']),
legend: (fieldset['legend'] if fieldset['legend']),
hint: (fieldset['hint'] if fieldset['hint']),
required: (fieldset['required'] if fieldset['required']),
error: (fieldset['error'] if fieldset['error']),
options: (fieldset['options'] if fieldset['options'])
}.compact) %>
</div><% end %>
<% if region['formGroups'] %><div class="cfa-follow-up__region<% if region['modifier'] %> <%= region['modifier'] %><% end %>"
<% if region['id'] %> id="<%= region['id'] %>"<% end %>
<% if region['role'] %> role="<%= region['role'] %>"<% end %>
<% if region['ariaLabel'] %> aria-label="<%= region['ariaLabel'] %>"<% end %>>
<% region['formGroups'].each do |formGroup| %><div>
<% if formGroup['input'] %>
<%= ERB.new(File.read('packages/cfa-form-group/_cfa-form-group.html.erb'), 0, 0, [*('a'..'z')].sample(8).join)
.result_with_hash({
modifier: (formGroup['modifier'] if formGroup['modifier']),
id: (formGroup['id'] if formGroup['id']),
label: (formGroup['label'] if formGroup['label']),
hint: (formGroup['hint'] if formGroup['hint']),
error: (formGroup['error'] if formGroup['error']),
input: formGroup['input'],
inputGroup: (formGroup['inputGroup'] if formGroup['inputGroup'])
}.compact) %>
<% end %>
<% if formGroup['textarea'] %>
<%= ERB.new(File.read('packages/cfa-form-group/_cfa-form-group.html.erb'), 0, 0, [*('a'..'z')].sample(8).join)
.result_with_hash({
modifier: (formGroup['modifier'] if formGroup['modifier']),
id: (formGroup['id'] if formGroup['id']),
label: (formGroup['label'] if formGroup['label']),
hint: (formGroup['hint'] if formGroup['hint']),
error: (formGroup['error'] if formGroup['error']),
textarea: formGroup['textarea']
}.compact) %>
<% end %>
<% if formGroup['selectEl'] %>
<%= ERB.new(File.read('packages/cfa-form-group/_cfa-form-group.html.erb'), 0, 0, [*('a'..'z')].sample(8).join)
.result_with_hash({
modifier: (formGroup['modifier'] if formGroup['modifier']),
id: (formGroup['id'] if formGroup['id']),
label: (formGroup['label'] if formGroup['label']),
hint: (formGroup['hint'] if formGroup['hint']),
error: (formGroup['error'] if formGroup['error']),
selectEl: formGroup['selectEl']
}.compact) %>
<% end %>
<% if formGroup['memorableDate'] %>
<%= ERB.new(File.read('packages/cfa-memorable-date/_cfa-memorable-date.html.erb'), 0, 0, [*('a'..'z')].sample(8).join)
.result_with_hash({
modifier: (formGroup['modifier'] if formGroup['modifier']),
id: (formGroup['id'] if formGroup['id']),
fieldset: (formGroup['fieldset'] if formGroup['fieldset']),
required: (formGroup['required'] if formGroup['required']),
month: (formGroup['month'] if formGroup['month']),
day: (formGroup['day'] if formGroup['day']),
year: (formGroup['year'] if formGroup['year'])
}.compact) %>
<% end %>
<% if formGroup['options'] %>
<%= ERB.new(File.read('packages/cfa-fieldset/_cfa-fieldset.html.erb'), 0, 0, [*('a'..'z')].sample(8).join)
.result_with_hash({
modifier: (formGroup['modifier'] if formGroup['modifier']),
id: (formGroup['id'] if formGroup['id']),
legend: (formGroup['legend'] if formGroup['legend']),
hint: (formGroup['hint'] if formGroup['hint']),
required: (formGroup['required'] if formGroup['required']),
error: (formGroup['error'] if formGroup['error']),
options: (formGroup['options'] if formGroup['options'])
}.compact) %>
<% end %>
</div><% end %>
</div><% end %>
</div>
Below is an example of how to include the partial in a view from the package directory using the Ruby ERB
class. Replace the argument values using your variables or context. In a Rails environment, the render
method can be used instead with the same hash values.
<%= ERB.new(File.read('packages/cfa-follow-up-question/_cfa-follow-up-question.html.erb'), 0, 0, '@followUpQuestion').result_with_hash({modifier: modifier, region: region, fieldset: fieldset}) %>