Current version 22.0.0

Forms

Development documentation

There are common prerequisites, dependencies, and requirements which need to be imported and applied to all form components. This documentation should be read alongside the Angular forms, NgModel and FormControl documentation.

Angular inputs and directives

All Tangram forms components support the Angular model-binding directives ngModel, formControl and formControlName (both Template-driven and Reactive forms are fully supported). Alongside this documentation, check out the Form Validation documentation.

All Tangram controls respond to standard Angular validation and status control, including [required] and [disabled].

required
boolean

Adds “required” validation. Tangram form components detect this binding to apply default required/optional messaging and validation.

disabled
boolean

Sets the enabled/disabled status of the associated Tangram form component.

Common inputs for Tangram form components

id
string

Sets a custom id for an input component. Each input will have an id generated automatically by default, but you can use [id] to override.

label
string

Required. Sets the label for this input component. All Tangram forms components must be given a human-readable label, whether it is made visible or not. This is to support screen-readers which will still read the label for a control even if it is visually hidden.

name
string

Required. Sets the name for this input component. This is required by NgModel and FormControl.

showLabel
boolean

If set to false, visually hides the label for this input. The label is still readable by screen readers.

showValidation
boolean

If set to false, the default validation messages will be hidden from the user. The control will still be invalid, so you must display a custom message.

Design Guidelines

Example

Forms are one of the most important parts of digital product design. When forms are designed with cohesion, customers are able to focus on completing the task at hand. Our 10 guidelines:

Use a single column layout to lead a customer through a form

  • A customer can more easily concentrate on a form if they only have a single direction to follow down the page. People can easily scroll vertically, however, scanning horizontally is significantly more difficult.
  • It may be tempting to fill white space on a page at larger breakpoints, however white space helps to maintain focus on a form.
  • Consider all breakpoints, to ensure the flow of a form is consistent.
Example of how you should stack labels above inputs making sure you only have one column of inputs
Do
Example to show that you should not put input fields side by side
Don't

Exceptions

Occasionally, a form may require a different layout. The vertical landing pages on Trade Me are an example of this:

  • These forms help customers get to search results quickly, and are effective in the larger context of the user journey.
  • The form fields are all optional, featuring closely related information, and can be completed in any order.
  • The form is one of many actions within the page. The vertical height is minimised to help balance with other content a customer may want to interact with.

When considering using an alternative form layout, consider:

  • Does the form have optional questions and a non-linear flow?
  • What does the page set out to achieve?
  • What is the user journey this form sits within?
  • Pay close attention to form interactions when user testing your layout.

Form actions align with the form layout

Example of an action link and a button

The primary button sits to the left, following the flow of the form inputs.

Example of equally weighted buttons

When options are equally weighted, buttons are displayed at equal width by using the grids.

A call to action is most effective when it describes intent

For tips on writing effective call to actions, read the microcopy guidelines.

Good example of call to action copy on buttons
Do
Bad example of call to action copy on buttons
Don't

Provide structure with form groups and headings so that the customer can move through the form without feeling overwhelmed.

Good example of how to highlight optional fields
Do
Bad example of grouping information
Don't

A short form is a good form

Consider ways to remove optional fields. Ask yourself if the question can be inferred, postponed, or whether there’s another way to collect data.

Use nested form content to simplify forms, and hide content until it is relevant.

Good example of nesting form content

Ditch the * and denote optional fields

When we only ask the questions we need to, most fields in a form are required. Rather than introducing visual clutter of indicating required fields, Tangram form components indicate when a field is optional.

Good example of how to highlight optional fields
Do
Bad example of how to highlight optional fields
Don't

Use input sizes to help indicate the desired content

This is particularly helpful for inputs that have a defined character count.

Good example of input size to indicate content
Do
Bad example of input size to indicate content
Don't

Use the appropriate component for the number of options available

  • For a choice between on or off, use a switch.
  • When you have five or fewer options, use a radio group, checkbox group or a toggle.
  • If there are over five options, use a select.
  • If there are over 25 options, use components to build a search filter.

If there are over 25 options, build a search filter.

Example pf what the search filter should look like

Resist using placeholder text as labels

Using placeholder text instead of labels removes the accessibility and usability benefits that Tangram provides. Assistive technologies work best when a well structured label and input are used.

Read more about usability concerns of replacing labels with placeholder text.

Good example of form labels where the labels are above the form inputs
Do
Bad example of form labels where placeholder text is used as the label
Don't

Help text is only helpful when it is visible

Tangram form components provide a placement for support text that doesn’t require customer interaction.

Good example of help text where it is below the input field
Do
Bad example of help text where it is in a hover 'i'
Don't

Checkbox

import { TgCheckboxComponent, TgCheckboxNoteDirective, TgCheckboxGroupComponent, TgCheckboxRequiredDirective, TgCheckboxGroupRequiredDirective, TgCheckboxLabelInfoDirective } from '@trademe/tangram';

Enhance a single checkbox with Tangram styling and transitions.

When using multiple checkboxes, use checkbox group .

Example

Behaviour

  • Checkboxes can contain nested form content.
  • The checkbox touch area includes both the checkbox and label area.
  • It is possible to nest controls within a Checkbox. Progressive disclosure can be implemented by using *ngIf to hide or show the nested content based on selections. If disabling nested controls you must manually bind disabled on any nested content.

Indeterminate

The indeterminate state represents a partial selection. It is typically used when some, but not all, child checkboxes in a group are selected. This state is purely visual and cannot be set by the user directly, it must be set programmatically.

Usage guidelines

  • Use the indeterminate state when a parent checkbox reflects the mixed selection of its children.
  • Do not allow users to manually place a checkbox in an indeterminate state, it should only reflect underlying data.
  • Toggling the indeterminate checkbox should select or deselect all child items and remove the indeterminate state.

Behaviour

  • Visual: Display as a checkbox with a horizontal line (or dash) instead of a checkmark.
  • Interaction:
    • Clicking an indeterminate checkbox should toggle all associated child checkboxes to either checked or unchecked.
    • After interaction, the checkbox should move to a determinate state (checked or unchecked).

Checkbox group

import { TgCheckboxComponent, TgCheckboxNoteDirective, TgCheckboxGroupComponent, TgCheckboxRequiredDirective, TgCheckboxGroupRequiredDirective, TgCheckboxLabelInfoDirective } from '@trademe/tangram';

Checkbox group allows more than one option to be selected at the same time.

For individual checkboxes, use a checkbox.

Example

Checkbox group label

Behaviour

  • Checkboxes within checkbox groups can contain nested form content.
  • Checkbox group uses a native fieldset to wrap items.
  • A checkbox group has margin-bottom to separate it from the content below, and removes the margin-bottom from the individual checkboxes.

Colour picker

A colour picker allows users to select colour(s). You can insert a colour picker using either tg-radio (for selecting a single colour) or tg-checkbox (for selecting multiple colours). Use the color option available with either of these components.

The colours available are limited to beige, black, blue, brown, cream, green, grey, orange, pink, purple, red, white, yellow, silver, gold and multi.

Example

Single select

Using radio group

(optional)

Multi select

Using checkbox

Checkbox color group

File uploader

import { TgFileUploaderComponent, TgFileUploaderRequiredDirective, TgFileUploaderAcceptDirective } from '@trademe/tangram';

File uploader allows customers to upload files.

Example

Behaviour

  • Clicking or tapping inside file uploader or drop zone prompts the native file uploader.
  • File uploader will only handle interactive states for tgButton2 and tg-dropzone when either is used in the content slot.
  • File uploader and drop zone is full width by default. To limit the size place it inside a tg-col.
  • When using drop zone without file uploader, you will need to implement drag and drop event handling.
  • The tg directive enables highlighting of the dropzone when the user is dragging on the page, but not directly over the drop zone.

Accessibility

  • When a button is used with file uploader it must be removed from the tabindex using tabindex='-1' as the user is interacting with the <tg-file-uploader> and not the button.

Depedencies

  • ES6 string functions startsWith and endsWith are used in file uploader. As these are not supported by Internet Explorer 11, a polyfill such as core-js is needed.

Form group

import { TgFormGroupComponent, TgFormGroupRequiredDirective } from '@trademe/tangram';

Form group wraps around a group of related inputs. It is used to validate whether the group is collectively valid. Form group validation messages only show once all of the individual inputs are valid. See forms validation for information on validation options.

Form group must be used with a directive that implements ControlContainer, for example ngModelGroup or formGroupName.

In this example, height and width are validated as numbers. We have added a custom validation function to ensure that the total area does not exceed 1,000.

Example

Form group label

Behaviour

  • If a disabled state is set on a <tg-form-group>, all child inputs will be disabled. If dynamically toggled to enabled, all child inputs will be enabled again, regardless of their original state. If no disabled state is set on the <tg-form-group>, the child inputs will handle their own disabled state.

Input container

import { TgInputDirective, TgInputContainerComponent, TgTextAreaComponent, TgCurrencyDirective } from '@trademe/tangram';

Input container extends the features and styles of native text inputs. Input and textarea elements that are wrapped by a <tg-input-container> and must use the text input directive.

Example

Behaviour

  • At the sm breakpoint the half, onethird, onequarter size options all default to 50% width of their parent container.
  • When using <input>, content will overflow to the right of the input. To avoid content overflow or to suggest multi-line input to the user, use Textarea.

Input group

import { TgInputGroupComponent } from '@trademe/tangram';

Input group is used to group two or more related form controls.

Input group can be used as <tg-input-group>, or <fieldset tg-input-group> when fieldset is appropriate.

Example

Nested form content

Nested form content can be used to gather further information based on a customer’s selection. Nested content can be used with checkbox, radio group, and select container.

Any content can be nested in this content slot. <tg-input-container> is used in this example.

Nested inputs can be shown or hidden based on a customer’s interaction with a form. Progressive disclosure can be implemented by using *ngIf.

You must manually bind disabled on any nested form fields. Disabled bindings from the parent element are not automatically applied to child elements.

Example

Usage guidelines

Bad example of nesting form content repeatedly
Don't

Avoid repeating the same nested form input under multiple options. Instead, look at restructuring form content.

Bad example of multiple level nesting form content
Don't

Avoid multiple levels of nesting.

Text input directive

The tgTextInput directive is applied to native text inputs and textareas inside an input container. It can work with other input directives from Angular, Tangram and custom code to extend the features and styles of the input.

Behaviour

  • Unless "required" validation is added, all text inputs will be considered optional.
  • Other native input attributes (e.g. autocomplete, placeholder, or autocapitalize) can be used.
  • All inputs can be disabled using the standard Angular disabled directive.
  • name is required by ngModel, where it is used as the control name by the Angular FormGroup API.

Validation

Tangram provides a number of validators which apply to text inputs. These can be used with a tgTextInput or a native input.

Currency formatting

Adding the currency directive to an input enables automatic currency formatting on the input (in addition to enabling currency validation).

currency

Validates a currency value, and enables automatic currency formatting.

currencyMin

Validates the entered currency value is at least the set value. Displays a “currency” error message.

currencyMax

Validates the entered currency value is at most the set value. Displays a “currency” error message.

Currency behaviour

  • Currency enables automatic currency formatting when the control blurs. The ngModel value emitted is a number value accurately representing the entered number. The auto-format is flexible and discards additional whitespace and punctuation, but will consider any non-numeric text to be invalid.
  • The currency directive enforces the input type=”tel”. On touch devices the numeric keyboard will be triggered. Manually setting the input type attribute when using currency will raise an error.
  • Numbers will always be rounded to 2 decimal places (cents).
  • Valid ngModel outputs will always be a valid float. Invalid inputs will be bound to NaN.

Textarea

Use <textarea> for multi-line text inputs, ensuring the tgTextInput directive is applied, and is nested inside a <tg-input-container>.

Example

Behaviour

Avoid visual bugs by ensuring there is no whitespace between the opening and closing tags of a <textarea>.

Radio group

import { TgRadioGroupComponent, TgRadioItemComponent, TgRadioLabelInfoDirective, TgRadioNoteDirective } from '@trademe/tangram';

Radios are a list of mutually exclusive options where it is important to present all available choices. Add an option of 'none' if there needs to be an unselected state.

Consider using a select when there are more than five options.

Example

Radio group label (optional)

Behaviour

Radio items within a radio group can contain nested form inputs.

Range slider

import { TgRangeSliderComponent, SliderRange } from '@trademe/tangram';

SliderRange is only needed if you want TypeScript type checking for your range values. It can be omitted if you don't need typing.

Range slider is a range control with dual handles and number inputs for precise value entry.

The range slider component provides a visual range selector with two draggable thumbs for minimum and maximum values. Users can interact with the slider by dragging the handles, using keyboard navigation, or entering values directly into the number inputs.

Example

Behaviour

  • When the minimum value equals the lower boundary, or the maximum value equals the upper boundary, the input displays "Any" as an overlay. This overlay is hidden when the input is focused to allow typing.
  • A "Clear" button appears when values differ from the range boundaries. Clicking it resets both minimum and maximum to the full range.
  • The component validates that the minimum value is lower than the maximum value. When min >= max, both inputs display validation errors and the slider thumbs freeze at their last valid positions with a visual invalid state indicator.
  • All values automatically snap to the configured step increment, whether adjusted by keyboard, drag, or direct input.
  • Users can click anywhere on the track to move the nearest thumb to that position.
  • Keyboard navigation is supported on the slider thumbs:
    • Arrow Left/Down: decrease by one step
    • Arrow Right/Up: increase by one step
    • Page Down: decrease by 10 steps
    • Page Up: increase by 10 steps
    • Home: jump to minimum boundary
    • End: jump to maximum boundary
  • Number inputs allow precise value entry. Clearing an input resets that value to its boundary (minimum or maximum). Values are automatically clamped to the configured min-max range.
  • Screen readers announce thumb values using aria-valuetext. By default, values at boundaries are announced as "Any". Use the ariaValueTextFn input to provide custom formatting (e.g., currency or percentage formatting).

Select container

import { TgSelectDirective, TgSelectContainerComponent } from '@trademe/tangram';

Select container extends the features of a native select element.

Selects enable customers to choose a single item from a list of items. They are suitable when there are many options to choose from. Consider using radio where possible, as they allow users to see all available options at a glance.

Example

Behaviour

Select containers can contain nested form inputs.

Switch

import { TgSwitchComponent } from '@trademe/tangram';

Use a switch to make a choice between two binary options, such as on/off.

Example

Behaviour

  • Switches are always left-aligned with the label sitting to the right of the switch.
  • The tappable area is the entire row the switch sits in.
  • Use a tg-form-group or fieldset to group switches.

Toggle

import { TgToggleComponent, TgToggleItemComponent } from '@trademe/tangram';

Example

Toggle label (optional)

Behaviour

  • By default, toggle fills the width of its container with evenly spaced toggle items.
  • Pre-select a toggle item by using two-way binding on ngModel on the parent tgToggle.
  • Toggles are a radio group with button-like styling.

Validation

Example

Tangram provides validation directives for common situations. These are used alongside Angular validators.

Required items

These validators only run when a value is provided. An empty input field will be valid. Use the Angular required validator if a value is required.

Validation directives
TypeMethodDescription
Currency [currency]Validates that the input string contains a number formatted as New Zealand currency.
[currencyMin]: numberValidates that the string is at most the given value.
[currencyMax]: numberValidates that the string is at least the given value.
Email[email]: string Validates that the given string contains an @ and at least one subdomain. Inputs with type='email' automatically have email validation applied.
Number [number]Validates that the given string is a valid number.
[numberMax]: numberValidates that the input string is at most the given value.
[numberMin]: numberValidates that the input string is at least the given value.
Postcode[postcode]Validates that the value is a New Zealand postcode of four digits.

Validation summary

import { TgValidationSummaryComponent, TgValidationSummaryFormRefDirective } from '@trademe/tangram';

Validation summary provides up-to-date information of any validation issues on a form. It is especially useful on long forms as it helps the user to discover validation issues.

Place validation summary above the submit button.

Validation summary appears when an invalid form is submitted. To observe this, submit the example form below without entering content.

Example

Behaviour

The content inside validation summary automatically updates as issues are resolved, and disappears when all issues have been resolved.

Custom validation directive

import { TgValidationDirective, TgValidationMessagesComponent, ValidationMessagesPipe } from '@trademe/tangram';

Use this directive when creating your own custom validation.

The validation directive can be applied to any form input or group with ngModel or ngModelGroup. This is an abstraction on the native Angular custom validation as a way to simplify the process of creating custom validation.

Example

Directive

validation
ValidationConfig | null

Defaults to null. For more information regarding ValidationConfig see interfaces below.

Example

<input
    ngModel
    tgTextInput
    [validation]="{
        required: 'Example input is required',
        myCustomValidator: {
            message: 'Example input business rule is invalid',
            validator: myCustomValidator
        }
    }"
/>

Validator interfaces

These are provided by Tangram to allow you to create your own custom validators.

ValidationConfig

interface ValidationConfig {
    [validation: string]: ValidatorConfig | string;
}

ValidatorConfig

interface ValidatorConfig {
    message?: ValidatorMessageFn | string;
    validator?: AsyncValidatorFn | ValidatorFn;
}

Note: AsyncValidatorFn and ValidatorFn are Angular interfaces.

ValidatorMessageFn

type ValidatorMessageFn = () => string;
Proudly made by © 2026 Trade Me Limited