Airframe GitHub Storybook
Component

ba-select

A form field for giving users a choice from a dropdown

Airframe

Active
Figma

Web Components

Active
Storybook Github

React

Active
Storybook Github

React Native

Planned
Design Code Accessibility QA
A

Label

A short, descriptive text that identifies the purpose of the select field

B

Hint text (optional)

Additional guidance below the label to help users understand what to select

C

Select input

The interactive dropdown control that reveals a list of options when activated

D

Error message

Inline validation feedback that appears when no valid option has been selected

It is best to use the select component when you have 5 or more options for the user to select from. If you have fewer options to select from, try using ba-radio-group instead.

Where a natural order exists, follow it. For example:

  • Alphabetical: Lists of countries, names, etc
  • Numerical: Seat numbers, baggage counts, etc

If no clear ordering principle applies (e.g., different methods for downloading a boarding pass), consider:

  • Grouping options by category with <optgroup> elements for example, dividing meal preferences into Vegetarian, Non-Vegetarian, and Special Diet - helps users quickly scan and select the option that suits their needs
  • Alphabetical ordering if no other logical sequence makes sense
  • Short, descriptive labels that clearly differentiate each option
<ba-select
    label="Select Label"
    hint-text="Help text for the field"
    name="name"
    prompt-text="Choose an item"
    required
  >
  <p slot="error">Custom error message</p>

  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
  <option value="6">Option 6</option>
  <option value="7">Option 7</option>
  <option value="8">Option 8</option>
  <option value="9">Option 9</option>
  <option value="10">Option 10</option>
</ba-select>
Property Attribute Description Type Default
autocomplete autocomplete The autocomplete setting string | undefined 'off'
hintText hint-text Hint text to show above the input string | undefined undefined
iconName icon-name The name of the icon to show in the media slot. Will be overridden if there is content in the media slot. string | undefined undefined
inputWidth input-width The width of the input in characters string | undefined undefined
invalid invalid The name of the input boolean | undefined false
label(required) label A label for the input string undefined
name(required) name The name of the input string undefined
promptText prompt-text Text for the default option string | undefined undefined
required required Whether the input is required boolean | undefined false
value value The value of the input string | undefined ''
  • None
Slot Description Permitted elements
Unnamed slot The options for the select will be rendered here <option>
"error" Element will be rendered in the error slot <p>
"media" Show an image or icon before the text <ba‑image>, <img>, <svg>

ba-select can be slotted into:

Event Description Type
baBlur Emitted when the select loses focus. CustomEvent<void>
baChange Emitted when the value has changed. CustomEvent<SelectChangeEventDetail>
baFocus Emitted when the select has focus. CustomEvent<void>
Method Description Type
isValid() => Promise<boolean> Triggers required validation and resolves with validity state. Promise<boolean>
reset() => Promise<void> Resets the select back to its initial value and validity. Promise<void>

The first option will be selected if no prompt-text and value are set, and each option has a value.

This will always pass required validation as value is set via the component internally.

Basic example with a range of options to select from.
<ba-select
    label="Select Label"
    hint-text="Help text for the field"
    name="name"
    required
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
  <option value="6">Option 6</option>
  <option value="7">Option 7</option>
  <option value="8">Option 8</option>
  <option value="9">Option 9</option>
  <option value="10">Option 10</option>
</ba-select>

When the prompt-text has been set this will be selected by the component on page load.

This will fail the required validation as no valid value has been selected.

Setting prompt-text
<ba-select
    label="Select Label"
    hint-text="Help text for the field"
    name="name"
    prompt-text="Choose an item"
    required
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
  <option value="6">Option 6</option>
  <option value="7">Option 7</option>
  <option value="8">Option 8</option>
  <option value="9">Option 9</option>
  <option value="10">Option 10</option>
</ba-select>

You can provide your own custom validation and error message using events, props and slots.

Custom validation and error message
<ba-select
    label="Select Label"
    hint-text="Help text for the field"
    name="name"
    prompt-text="Choose an item"
    required
  >
  <p slot="error">Custom error message</p>

  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
  <option value="6">Option 6</option>
  <option value="7">Option 7</option>
  <option value="8">Option 8</option>
  <option value="9">Option 9</option>
  <option value="10">Option 10</option>
</ba-select>

<script>
  const baSelect = document.querySelector('ba-select');
  baSelect.addEventListener('baChange', (e) => {
    const value = e.detail.value;

    // Do your custom validation here.
    //
    //
    // Then set the input to invalid
    baSelect.setAttribute('invalid')
  })
</script>

For adding / removing options you need to remove all existing options and re-populate them to ensure the internal state of the component is correctly updated.

Ensure that you are interacting with ba-select and not a wrapped native select element.

Replacing all options via JavaScript
<ba-select
    label="Select Label"
    prompt-text="Choose an item"
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
</ba-select>

<script>
  const baSelect = document.querySelector('ba-select');
  const currentOptions = baSelect.querySelectorAll('option');

  currentOptions.forEach(option => option.remove());

  const options = [
    { value: 'a', text: 'Option A' },
    { value: 'b', text: 'Option B' },
    { value: 'c', text: 'Option C' },
  ];

  options.forEach(option => {
    const optionElement = document.createElement('option');
    optionElement.setAttribute('value', option.value);
    optionElement.textContent = option.text;
    baSelect.appendChild(optionElement);
  });

  // Optional, you can set a value after updating options
  baSelect.value = 'b';
</script>

There is an option to show an icon within the select component. Simply add the name of the icon you wish to present in the icon-name prop.

Showing an icon within the select
<ba-select
    label="Select Label"
    hint-text="Help text for the field"
    name="name"
    required
    icon-name="add"
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
</ba-select>

You can also use the media slot by using a svg, img or ba-image element.

Using the media slot
<ba-select
    label="Select Label"
    hint-text="Help text for the field"
    name="name"
    required
    icon-name="add"
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <ba-image slot="media" src="path-to-image"></ba-image>
</ba-select>

You can dynamically change the image based on the user's selection.

Dynamically changing the media
<ba-select
  label="Select Label"
  hint-text="Help text for the field"
  name="name"
  required
  prompt-text="Choose an item"
>
  <img src="visa.webp" alt="Visa" id="visa" slot="media" style="display: none;"/>
  <img src="mastercard.webp" alt="Mastercard" id="mastercard" slot="media" style="display: none;"/>
  <img src="american-express.webp" alt="American Express" id="amex" slot="media" style="display: none;"/>
  <img src="apple-pay.webp" alt="Apple Pay" id="apple" slot="media" style="display: none;"/>
  <img src="google-pay.webp" alt="Google Pay" id="google" slot="media" style="display: none;"/>

  <option value="visa">Visa</option>
  <option value="mastercard">Mastercard</option>
  <option value="amex">Amex</option>
  <option value="apple">Apple Pay</option>
  <option value="google">Google Pay</option>
</ba-select>

<script>
  (() => {
    let previousValue = '';
    document.addEventListener('baChange', (event) => {
      const paymentType = event.detail.value;

      if (previousValue !== '') {
        const previousElement = document.getElementById(previousValue);
        previousElement.style.display = 'none';
      }

      if (paymentType !== '') {
        const element = document.getElementById(paymentType);
        element.style.display = 'block';
        previousValue = paymentType;
      }
    })
  })()
</script>
Prop Description Type Default
autocomplete The autocomplete setting string | undefined 'off'
hintText Hint text to show above the input string | undefined undefined
iconName The name of the icon to show in the media slot. Will be overridden if there is content in the media slot. string | undefined undefined
inputWidth The width of the input in characters string | undefined undefined
invalid The name of the input boolean | undefined false
label (required) A label for the input string undefined
name (required) The name of the input string undefined
promptText Text for the default option string | undefined undefined
required Whether the input is required boolean | undefined false
value The value of the input string | undefined ''
children The options for the select will be rendered here <option>
error Element will be rendered in the error slot <p>
media Show an image or icon before the text <ba‑image>, <img>, <svg>

None

BaSelect can be slotted into:

Events Description
onBaBlur Emitted when the select loses focus.
onBaChange Emitted when the value has changed.
onBaFocus Emitted when the select has focus.
Method Description
isValid() => Promise<boolean> Triggers required validation and resolves with validity state.
reset() => Promise<void> Resets the select back to its initial value and validity.

The first option will be selected if no prompt-text and value are set, and each option has a value.

This will always pass required validation as value is set via the component internally.

Basic example with a range of options to select from.
<BaSelect
    label="Select Label"
    hintText="Help text for the field"
    name="name"
    required
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
  <option value="6">Option 6</option>
  <option value="7">Option 7</option>
  <option value="8">Option 8</option>
  <option value="9">Option 9</option>
  <option value="10">Option 10</option>
</BaSelect>

When the prompt-text has been set this will be selected by the component on page load.

This will fail the required validation as no valid value has been selected.

Setting prompt-text
<BaSelect
    label="Select Label"
    hintText="Help text for the field"
    name="name"
    promptText="Choose an item"
    required
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
  <option value="6">Option 6</option>
  <option value="7">Option 7</option>
  <option value="8">Option 8</option>
  <option value="9">Option 9</option>
  <option value="10">Option 10</option>
</BaSelect>

You can provide your own custom validation and error message using events and props.

Custom validation and error message
export default function CustomValidationExample() {
  const [invalid, setInvalid] = useState(false);

  const handleChange = (value: string, _event: ChangeEvent<HTMLSelectElement>) => {
    // Do custom validation here

    // Set the input to invalid
    setInvalid(true)
  }
  return (
<BaSelect
    label="Select Label"
    hintText="Help text for the field"
    name="name"
    promptText="Choose an item"
    required
    invalid={invalid}
    error="Custom error message"
    onBaChange={handleChange}
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
  <option value="6">Option 6</option>
  <option value="7">Option 7</option>
  <option value="8">Option 8</option>
  <option value="9">Option 9</option>
  <option value="10">Option 10</option>
</BaSelect>
  )
}

You can also use the media slot by using a svg, img or BaImage element.

Using the media slot
<BaSelect
    label="Select Label"
    hintText="Help text for the field"
    name="name"
    required
    media={<BaImage src="path-to-image"/>}
  >
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
</BaSelect>

You can dynamically change the image based on the user's selection.

Dynamically changing the media
export const DynamicImages = {
  args: {
    media: (
      <BaImage
        src="demo-images/16-9.webp"
        alt="Example with ba-image"
        aspectRatio="16-9"
      />
    ),
  },
  render: (args) => {
    const [mediaObject, setMediaObject] = useState<React.ReactNode>(null);

    const onChangeHandler = (
      value: string,
      ev: React.ChangeEvent<HTMLSelectElement>,
    ) => {
      args.onBaChange?.(value, ev);
      if (value === "") {
        setMediaObject(null);
        return;
      }

      setMediaObject(
        <img
          src={`demo-images/patterns/${value}.webp`}
          alt={`Example with ba-image for ${value}`}
        ></img>,
      );
    };
    return (
      <BaSelect
        label="Select with dynamic images"
        name="select"
        hintText="Help text for the field"
        promptText="Choose an item"
        required={true}
        onBaChange={onChangeHandler}
        media={mediaObject}
      >
        <option value="visa">Visa</option>
        <option value="mastercard">Mastercard</option>
        <option value="american-express">Amex</option>
        <option value="apple-pay">Apple Pay</option>
        <option value="google-pay">Google Pay</option>
      </BaSelect>
    );
  },
};

React Native documentation coming soon.

Always use the label attribute to give a meaningful label to the field.

Further reading:

Disabled form elements are not supported in BAgel because they create accessibility challenges, such as preventing keyboard navigation, confusing screen reader users, and reducing visual clarity for those with impairments

Further reading:

State: Previous element in DOM has focus

⇥ Tab

<ba-select> gets focus

State: <ba-select> has focus

⏎ Enter

Opens the dropdown list of options

␣ Space

Opens the dropdown list of options

⇥ Tab

Next tabbable element in the DOM gets focus

State: Dropdown is open

↑ Arrow Up

Moves highlight to the previous option

↓ Arrow Down

Moves highlight to the next option

⏎ Enter

Selects the highlighted option, closes the dropdown, and emits baChange

Escape

Closes the dropdown without changing the selected value

Use this checklist to confirm the component has been configured and used correctly.

  • ba-select is used when there are 5 or more options to choose from
  • ba-radio-group is used instead when there are fewer than 5 options
  • Options are presented in a meaningful order (alphabetical, numerical, or grouped with optgroup)
  • prompt-text is used when the field is required and no default selection should be pre-applied
  • Labels are concise and clearly describe what the user is choosing
  • A visible label is always provided using the label attribute
  • The disabled state is not used — ba-select does not support disabled form controls
  • ba-select is keyboard operable and receives a visible focus indicator
  • Focus indicator has not been obscured on all sides of the component

These checks are handled by the component and do not need to be repeated each time it is used.

  • ba-select is operable using assistive technology
  • Has a visible focus indicator on all sides of the component
  • Colour contrast for all states in all BAgel themes
  • Animations respect users' reduced motion settings
  • High contrast mode adjustments
Designing Developing Components BAgel helper QA process britishairways.com Careers Cookie policy