UNPKG

@funidata/ngx-fudis

Version:

Funidata Design System.

1,207 lines (937 loc) 49.6 kB
# @funidata/ngx-fudis — AI Agent Reference > Angular component library by Funidata. This file is shipped with the npm package to help AI coding agents use Fudis components correctly. > > **Angular:** >=20 | **Peer deps:** `@angular/cdk`, `@angular/material`, `@funidata/fudis-core` > > **Source & stories:** https://github.com/funidata/fudis | **Storybook:** https://fudis.funidata.fi --- ## Table of Contents - [Setup](#setup) - [Validators](#validators) - [Composition Rules](#composition-rules) - [Components](#components) - [Button](#button) - [Icon Button](#icon-button) - [Icon](#icon) - [Heading](#heading) - [Body Text](#body-text) - [Grid](#grid) - [Grid Item](#grid-item) - [Section](#section) - [Expandable](#expandable) - [Form](#form) - [Fieldset](#fieldset) - [Text Input](#text-input) - [Text Area](#text-area) - [Select](#select) - [Multiselect](#multiselect) - [Datepicker](#datepicker) - [Date Range](#date-range) - [Checkbox](#checkbox) - [Checkbox Group](#checkbox-group) - [Radio Button Group](#radio-button-group) - [Localized Text Group](#localized-text-group) - [Dialog](#dialog) - [Dropdown Menu](#dropdown-menu) - [Notification](#notification) - [Alert Group](#alert-group) - [Badge](#badge) - [Description List](#description-list) - [Breadcrumbs](#breadcrumbs) - [Tab Navigation](#tab-navigation) - [Pagination](#pagination) - [Loading Spinner](#loading-spinner) - [Footer](#footer) - [Horizontal Rule](#horizontal-rule) - [Language Badge Group](#language-badge-group) - [Directives](#directives) - [Services](#services) - [Type Quick Reference](#type-quick-reference) --- ## Setup ```typescript import { NgxFudisModule } from "@funidata/ngx-fudis"; @NgModule({ imports: [NgxFudisModule], }) export class AppModule {} ``` Import in your main SCSS: ```scss @use "@funidata/ngx-fudis" as fudis; ``` --- ## Validators Fudis uses its own validators that wrap Angular's built-in ones. **You must use `FudisValidators` instead of Angular's `Validators`** because Fudis components read error messages from the validator metadata. ```typescript import { FudisValidators, FudisGroupValidators } from '@funidata/ngx-fudis'; // FormControl validators control = new FormControl(null, [ FudisValidators.required('This field is required'), FudisValidators.minLength(3, 'Minimum 3 characters'), FudisValidators.maxLength(100, 'Maximum 100 characters'), FudisValidators.email('Invalid email format'), FudisValidators.min(0, 'Must be positive'), FudisValidators.max(999, 'Too large'), FudisValidators.pattern(/^[a-z]+$/, 'Only lowercase letters'), ]); // Datepicker validators dateControl = new FormControl(null, [ FudisValidators.datepickerMin({ value: new Date('2024-01-01'), message: 'Too early' }), FudisValidators.datepickerMax({ value: new Date('2025-12-31'), message: 'Too late' }), ]); // FormGroup validators (for CheckboxGroup, LocalizedTextGroup) formGroup = new FormGroup({ ... }, [ FudisGroupValidators.oneRequired('Select at least one'), FudisGroupValidators.min({ value: 2, message: 'Select at least 2' }), FudisGroupValidators.max({ value: 5, message: 'Select at most 5' }), ]); ``` Messages can be `string` or `Observable<string>` for dynamic translations. --- ## Composition Rules These constraints are enforced at runtime and are **not optional**: | Child | Must be inside | Notes | | ---------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- | | `fudis-dropdown-menu` | `fudis-icon-button` | Parent must have `[asMenuButton]="true"` | | `fudis-breadcrumbs-item` | `fudis-breadcrumbs` | Uses `@Host()` injection | | `fudis-datepicker` (in range) | `fudis-date-range` | Uses `@Host() @Optional()` — optional | | `fudisFormSubmit` | `fudis-button` | Directive applied on a `fudis-button`; button must be inside a `fudis-form` | | `fudis-select-option` | `fudis-select` | Content projected via `fudisSelectOptions` directive | | `fudis-multiselect-option` | `fudis-multiselect` | Content projected via `fudisSelectOptions` directive | | `fudis-select-group` | `fudis-select` or `fudis-multiselect` | Groups options visually | | `fudis-checkbox-group-option` | `fudis-checkbox-group` | Child provides `[controlName]` matching FormGroup key | | `fudis-radio-button` | `fudis-radio-button-group` | Child provides option value+label | | `fudis-tab-navigation-tab` | `fudis-tab-navigation-bar` | Attribute selector: `<a fudis-tab-navigation-tab>` or `<button fudis-tab-navigation-tab>` | | Form content directives (`fudis-form-content`, etc.) | `fudis-form` | See [Directives](#directives) | ### Dialog must be opened via service ```typescript // Dialog is NOT used as inline HTML. Open it via FudisDialogService: constructor(private dialogService: FudisDialogService) {} openDialog() { this.dialogService.open(MyDialogComponent, { size: 'md' }); } ``` Inside the dialog component template, use `fudis-dialog` with content directives: ```html <fudis-dialog [size]="'md'"> <fudis-heading fudisDialogTitle>Title here</fudis-heading> <fudis-dialog-content>Content here</fudis-dialog-content> <fudis-dialog-actions> <fudis-button fudisDialogClose [label]="'Close'"></fudis-button> </fudis-dialog-actions> </fudis-dialog> ``` --- ## Components ### Button **Selector:** `fudis-button` | Input | Type | Default | Required | Description | | ----------------- | --------------------------------------------- | ----------- | -------- | ------------------------------------ | | `label` | `string` | — | **yes** | Text content of the button | | `variant` | `'primary' \| 'secondary' \| 'tertiary'` | `'primary'` | no | Button style variant | | `size` | `'small' \| 'medium'` | `'medium'` | no | Button size (excludes 'extra-small') | | `type` | `'submit' \| 'button'` | `'button'` | no | HTML button type | | `disabled` | `boolean` | `false` | no | Disables the button | | `icon` | `FudisIcon` | `undefined` | no | Icon displayed in button | | `iconRotate` | `'flip-180' \| 'cw-90' \| 'ccw-90' \| 'none'` | `'none'` | no | Icon rotation | | `popoverText` | `string` | — | no | Popover tooltip text | | `popoverPosition` | `'left' \| 'right' \| 'above' \| 'below'` | `'below'` | no | Popover position | | Output | Type | Description | | ------------- | -------------------------- | ----------- | | `handleClick` | `EventEmitter<Event>` | Click event | | `handleFocus` | `EventEmitter<FocusEvent>` | Focus event | | `handleBlur` | `EventEmitter<FocusEvent>` | Blur event | ```html <fudis-button [label]="'Save'" [variant]="'primary'" (handleClick)="onSave()"></fudis-button> <fudis-button [label]="'Cancel'" [variant]="'secondary'" [size]="'small'"></fudis-button> <fudis-button [label]="'Search'" [icon]="'search'" [variant]="'tertiary'"></fudis-button> ``` --- ### Icon Button **Selector:** `fudis-icon-button` | Input | Type | Default | Required | Description | | -------------- | ---------------------------------------- | ----------- | -------- | -------------------------------------- | | `ariaLabel` | `string` | — | **yes** | Accessible label for the button | | `icon` | `FudisIcon` | — | **yes** | Icon to display | | `asMenuButton` | `boolean` | `false` | no | Enables dropdown menu trigger behavior | | `variant` | `'primary' \| 'secondary' \| 'tertiary'` | `'primary'` | no | Style variant | | `size` | `'extra-small' \| 'small' \| 'medium'` | `'medium'` | no | Button size | | `disabled` | `boolean` | `false` | no | Disables the button | | Output | Type | Description | | ------------- | -------------------------- | ----------- | | `handleClick` | `EventEmitter<Event>` | Click event | | `handleFocus` | `EventEmitter<FocusEvent>` | Focus event | | `handleBlur` | `EventEmitter<FocusEvent>` | Blur event | ```html <fudis-icon-button [ariaLabel]="'Edit item'" [icon]="'edit'" [variant]="'tertiary'" (handleClick)="onEdit()" > </fudis-icon-button> <!-- As dropdown menu trigger --> <fudis-icon-button [ariaLabel]="'More actions'" [icon]="'three-dots'" [asMenuButton]="true"> <fudis-dropdown-menu> <fudis-dropdown-menu-item [label]="'Edit'" (handleClick)="edit()"></fudis-dropdown-menu-item> <fudis-dropdown-menu-item [label]="'Delete'" (handleClick)="delete()" ></fudis-dropdown-menu-item> </fudis-dropdown-menu> </fudis-icon-button> ``` --- ### Icon **Selector:** `fudis-icon` | Input | Type | Default | Required | | -------- | ------------------------------------------------------------------------------------------------------- | ------------- | -------- | | `icon` | `FudisIcon` | — | **yes** | | `color` | `'yellow' \| 'red' \| 'gray-dark' \| 'gray-light' \| 'primary' \| 'primary-dark' \| 'green' \| 'white'` | `'gray-dark'` | no | | `rotate` | `'flip-180' \| 'cw-90' \| 'ccw-90' \| 'none'` | `'none'` | no | ```html <fudis-icon [icon]="'search'" [color]="'primary'"></fudis-icon> ``` --- ### Heading **Selector:** `fudis-heading` | Input | Type | Default | Required | | --------- | -------------------------------------------------------- | --------------- | -------- | | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | — | **yes** | | `variant` | `'xxs' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl'` | auto from level | no | | `align` | `'left' \| 'right' \| 'center'` | `'left'` | no | | `id` | `string` | auto-generated | no | ```html <fudis-heading [level]="2" [variant]="'lg'">Page Title</fudis-heading> ``` --- ### Body Text **Selector:** `fudis-body-text` | Input | Type | Default | Required | | --------- | -------------------------------------------------------------------------- | -------------- | -------- | | `variant` | `'lg-regular' \| 'md-regular' \| 'sm-regular' \| 'lg-light' \| 'md-light'` | `'md-regular'` | no | | `align` | `'left' \| 'right' \| 'center'` | `'left'` | no | | `lang` | `'fi' \| 'sv' \| 'en'` | — | no | ```html <fudis-body-text>This is a paragraph of body text.</fudis-body-text> ``` --- ### Grid **Selector:** `fudis-grid` (component) or `fudisGrid` (directive on any element) | Input | Type | Default | Description | | ------------- | ------------------------------------------------------------------------------- | -------------- | ---------------------------------------------- | | `columns` | `string \| number \| { xs: ..., md: ..., lg: ... }` | `'1fr'` | CSS grid-template-columns or responsive object | | `align` | `'start' \| 'end' \| 'center'` | `'start'` | Grid alignment in parent | | `alignItemsX` | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'stretch'` | Horizontal alignment of items | | `alignItemsY` | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'stretch'` | Vertical alignment of items | | `columnGap` | `'none' \| 'xxs' \| 'xs' \| 'sm' \| 'md' \| 'lg' 'xl' \| 'xxl' \| 'responsive'` | `'responsive'` | Gap between columns | | `rowGap` | `'none' \| 'xxs' \| 'xs' \| 'sm' \| 'md' \| 'lg' 'xl' \| 'xxl' \| 'responsive'` | `'none'` | Gap between rows | | `width` | `'xxl' \| 'xl' \| 'lg' \| 'md' \| 'sm' \| 'xs' \| 'initial'` | `'xxl'` | Max width | | `classes` | `string` | — | Custom CSS classes | ```html <fudis-grid [columns]="{ md: '1fr 1fr', lg: '2fr 1fr 1fr' }" [rowGap]="'sm'"> <fudis-text-input ...></fudis-text-input> <fudis-text-input ...></fudis-text-input> <fudis-text-input ...></fudis-text-input> </fudis-grid> ``` --- ### Grid Item **Selector:** `fudis-grid-item` (component) or `fudisGridItem` (directive on any element) | Input | Type | Default | Description | | ------------ | ----------------------------------------------------------------- | ----------- | ------------------------- | | `columns` | `number \| string \| 'stretch' \| 'auto' \| { xs: ..., md: ... }` | `'auto'` | Column span | | `alignSelfX` | `'start' \| 'end' \| 'center' \| 'stretch'` | `'stretch'` | Horizontal self-alignment | | `alignSelfY` | `'start' \| 'end' \| 'center' \| 'stretch'` | `'stretch'` | Vertical self-alignment | ```html <fudis-grid [columns]="3"> <fudis-grid-item [columns]="2">Spans 2 columns</fudis-grid-item> <fudis-grid-item>Single column</fudis-grid-item> </fudis-grid> ``` --- ### Section **Selector:** `fudis-section` | Input | Type | Default | Required | | ------------------------ | --------------------------- | --------------- | -------- | | `title` | `string` | — | **yes** | | `level` | `1-6` | — | **yes** | | `titleVariant` | `FudisHeadingVariant` | auto from level | no | | `width` | `FudisGridWidth` | `'initial'` | no | | `align` | `FudisGridAlign` | `'start'` | no | | `badge` | `FudisBadgeVariant \| null` | — | no | | `badgeText` | `string \| null` | — | no | | `errorSummaryBreadcrumb` | `boolean` | `false` | no | | `classes` | `string` | — | no | | `popoverText` | `string` | — | no | ```html <fudis-section [title]="'Personal Info'" [level]="3"> <fudis-section-actions> <fudis-button [label]="'Edit'" [variant]="'secondary'"></fudis-button> </fudis-section-actions> <fudis-section-content> <p>Section content here</p> </fudis-section-content> </fudis-section> ``` --- ### Expandable **Selector:** `fudis-expandable` | Input | Type | Default | Required | | ------------------------ | --------------------------- | ----------- | -------- | | `title` | `string` | — | **yes** | | `level` | `number` | — | **yes** | | `variant` | `'regular' \| 'lite'` | `'regular'` | no | | `padding` | `'default' \| 'small'` | `'default'` | no | | `closed` | `boolean` | `true` | no | | `subTitle` | `string` | — | no | | `badge` | `FudisBadgeVariant \| null` | — | no | | `badgeText` | `string \| null` | — | no | | `errorSummaryBreadcrumb` | `boolean` | `false` | no | | Output | Type | | -------------- | ----------------------- | | `closedChange` | `EventEmitter<boolean>` | ```html <fudis-expandable [title]="'Additional Details'" [level]="3"> <fudis-expandable-actions> <fudis-button [label]="'Additional action'"></fudis-button> </fudis-expandable-actions> <ng-template fudisExpandableContent> <p>Hidden content revealed on expand.</p> </ng-template> </fudis-expandable> ``` --- ### Form **Selector:** `fudis-form` Extends Grid — all Grid inputs are available (`columns`, `rowGap`, `width`, etc.). | Input | Type | Default | Required | | --------------------- | -------------------------------------------------------- | ------------------ | -------- | | `title` | `string` | — | **yes** | | `level` | `1-6` | — | **yes** | | `titleVariant` | `'xxs' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl'` | auto from level | no | | `helpText` | `string` | — | no | | `badge` | `FudisBadgeVariant \| null` | — | no | | `badgeText` | `string \| null` | — | no | | `errorSummaryVisible` | `boolean` | `false` | no | | `errorSummaryTitle` | `string` | default Fudis text | no | | `id` | `string` | auto-generated | no | | Output | Type | | ------------------------ | ------------------------------------------------------- | | `handleUpdatedErrorList` | `EventEmitter<{id: string, message: string}[] \| null>` | ```html <fudis-form [title]="'Registration'" [level]="2" [errorSummaryVisible]="showErrors"> <fudis-form-content> <fudis-text-input [label]="'Name'" [control]="nameControl"></fudis-text-input> <fudis-text-input [label]="'Email'" [control]="emailControl"></fudis-text-input> </fudis-form-content> <fudis-form-actions> <fudis-button fudisFormSubmit [label]="'Submit'" [formValid]="form.valid"></fudis-button> </fudis-form-actions> </fudis-form> ``` --- ### Fieldset **Selector:** `fudis-fieldset` Groups related form fields with a `<fieldset>` and `<legend>`. | Input | Type | Default | Required | | ---------- | -------- | ------- | -------- | | `label` | `string` | — | **yes** | | `helpText` | `string` | — | no | ```html <fudis-fieldset [label]="'Address'"> <fudis-fieldset-content> <fudis-text-input [label]="'Street'" [control]="streetControl"></fudis-text-input> <fudis-text-input [label]="'City'" [control]="cityControl"></fudis-text-input> </fudis-fieldset-content> </fudis-fieldset> ``` --- ### Text Input **Selector:** `fudis-text-input` Inherits from TextFieldComponentBase → ControlComponentBase → FormCommonApi → PopoverApi. | Input | Type | Default | Required | Description | | -------------------------- | --------------------------------------------------------------- | -------- | -------- | ---------------------- | | `control` | `FormControl<string \| null \| number>` | — | **yes** | Reactive form control | | `label` | `string` | — | **yes** | Label text | | `type` | `'email' \| 'number' \| 'password' \| 'tel' \| 'text' \| 'url'` | `'text'` | no | HTML input type | | `size` | `'sm' \| 'md' \| 'lg'` | `'lg'` | no | Input width | | `helpText` | `string` | — | no | Help text below input | | `disabled` | `boolean` | `false` | no | Disable input | | `initialFocus` | `boolean` | `false` | no | Auto-focus on load | | `autocomplete` | `string \| string[]` | `'off'` | no | HTML autocomplete | | `name` | `string \| null` | `null` | no | HTML name attribute | | `step` | `number \| 'any' \| null` | `null` | no | Step for number inputs | | `nullControlOnEmptyString` | `boolean` | `true` | no | Set null instead of '' | | Output | Type | | ------------- | ----------------------------- | | `handleBlur` | `EventEmitter<FocusEvent>` | | `handleFocus` | `EventEmitter<FocusEvent>` | | `handleKeyUp` | `EventEmitter<KeyboardEvent>` | ```typescript nameControl = new FormControl<string | null>(null, [ FudisValidators.required("Name is required"), FudisValidators.maxLength(100, "Max 100 characters"), ]); ``` ```html <fudis-text-input [label]="'Full name'" [control]="nameControl" [helpText]="'Enter your legal name'" ></fudis-text-input> ``` --- ### Text Area **Selector:** `fudis-text-area` Same inputs as Text Input (minus `type`, `step`, `autocomplete`, `name`). ```html <fudis-text-area [label]="'Description'" [control]="descriptionControl" [helpText]="'Describe the issue in detail'" ></fudis-text-area> ``` --- ### Select **Selector:** `fudis-select` | Input | Type | Default | Required | | --------------------------- | ------------------------------------------------------------ | ------------ | -------- | | `control` | `FormControl<FudisSelectOption<T> \| null>` | — | **yes** | | `label` | `string` | — | **yes** | | `variant` | `'dropdown' \| 'autocompleteDropdown' \| 'autocompleteType'` | `'dropdown'` | no | | `size` | `'xs' \| 'sm' \| 'md' \| 'lg'` | `'lg'` | no | | `placeholder` | `string` | — | no | | `selectionClearButton` | `boolean` | `true` | no | | `autocompleteFilter` | `boolean` | `true` | no | | `autocompleteHelpText` | `string \| false` | — | no | | `autocompleteNoResultsText` | `string \| null` | `null` | no | | `helpText` | `string` | — | no | | Output | Type | | ---------------------- | -------------------------------------------- | | `selectionUpdate` | `EventEmitter<FudisSelectOption<T> \| null>` | | `filterTextUpdate` | `EventEmitter<string \| null>` | | `visibleOptionsUpdate` | `EventEmitter<number>` | **Important:** The `FormControl` value is a `FudisSelectOption` object, not a primitive. ```typescript import { FudisSelectOption } from '@funidata/ngx-fudis'; options: FudisSelectOption[] = [ { value: 'fi', label: 'Finland' }, { value: 'se', label: 'Sweden' }, { value: 'no', label: 'Norway' }, ]; countryControl = new FormControl<FudisSelectOption | null>(null, [ FudisValidators.required('Select a country'), ]); ``` ```html <fudis-select [label]="'Country'" [control]="countryControl"> <ng-template fudisSelectOptions> @for (option of options; track option.value) { <fudis-select-option [data]="option"></fudis-select-option> } </ng-template> </fudis-select> ``` With option groups: ```html <fudis-select [label]="'Country'" [control]="countryControl"> <ng-template fudisSelectOptions> <fudis-select-group [label]="'Nordic'"> <fudis-select-option [data]="{ value: 'fi', label: 'Finland' }"></fudis-select-option> <fudis-select-option [data]="{ value: 'se', label: 'Sweden' }"></fudis-select-option> </fudis-select-group> </ng-template> </fudis-select> ``` --- ### Multiselect **Selector:** `fudis-multiselect` Same inputs as Select, except: | Input | Type | Default | Required | | -------------------- | --------------------------------------------- | ------- | -------- | | `control` | `FormControl<FudisSelectOption<T>[] \| null>` | — | **yes** | | `showSelectionChips` | `boolean` | `true` | no | | Output | Type | | ----------------- | ---------------------------------------------- | | `selectionUpdate` | `EventEmitter<FudisSelectOption<T>[] \| null>` | ```typescript selectedCountries = new FormControl<FudisSelectOption[] | null>(null); ``` ```html <fudis-multiselect [label]="'Countries'" [control]="selectedCountries"> <ng-template fudisSelectOptions> @for (option of options; track option.value) { <fudis-multiselect-option [data]="option"></fudis-multiselect-option> } </ng-template> </fudis-multiselect> ``` --- ### Datepicker **Selector:** `fudis-datepicker` | Input | Type | Default | Required | | ------------ | ------------------------------ | ------- | -------- | | `control` | `FormControl<Date \| null>` | — | **yes** | | `label` | `string` | — | **yes** | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | no | | `dateParse` | `boolean` | `true` | no | | `dateFilter` | `(d: Date \| null) => boolean` | — | no | | `helpText` | `string` | — | no | ```typescript dateControl = new FormControl<Date | null>(null, [FudisValidators.required("Date is required")]); ``` ```html <fudis-datepicker [label]="'Start date'" [control]="dateControl"></fudis-datepicker> ``` --- ### Date Range **Selector:** `fudis-date-range` Wraps two `fudis-datepicker` components and validates that start <= end. | Input | Type | Default | | --------------------- | --------- | ------- | | `dateComparisonParse` | `boolean` | `true` | ```html <fudis-date-range> <fudis-datepicker fudisDateStart [label]="'Start'" [control]="startDate"></fudis-datepicker> <fudis-datepicker fudisDateEnd [label]="'End'" [control]="endDate"></fudis-datepicker> </fudis-date-range> ``` Use `fudisDateStart` and `fudisDateEnd` directives on the datepickers. --- ### Checkbox **Selector:** `fudis-checkbox` Standalone single checkbox (not part of a group). | Input | Type | Default | Required | | --------- | ------------- | ------- | -------- | | `control` | `FormControl` | — | **yes** | | `label` | `string` | — | no | ```html <fudis-checkbox [label]="'I agree to terms'" [control]="agreeControl"></fudis-checkbox> ``` --- ### Checkbox Group **Selector:** `fudis-checkbox-group` | Input | Type | Default | Required | | ----------- | -------------------------------------- | ------- | -------- | | `formGroup` | `FormGroup` | — | **yes** | | `label` | `string` | — | **yes** | | `size` | `'sm' \| 'md' \| 'lg' \| 'full-width'` | `'lg'` | no | | `helpText` | `string` | — | no | | Output | Type | | -------------- | --------------------------------------------- | | `handleChange` | `EventEmitter<FudisCheckboxGroupChangeEvent>` | ```typescript colorsGroup = new FormGroup( { red: new FormControl<boolean | null>(false), blue: new FormControl<boolean | null>(false), green: new FormControl<boolean | null>(false), }, [FudisGroupValidators.oneRequired("Select at least one color")], ); ``` ```html <fudis-checkbox-group [label]="'Favorite colors'" [formGroup]="colorsGroup"> <fudis-checkbox-group-option [controlName]="'red'" [label]="'Red'"></fudis-checkbox-group-option> <fudis-checkbox-group-option [controlName]="'blue'" [label]="'Blue'" ></fudis-checkbox-group-option> <fudis-checkbox-group-option [controlName]="'green'" [label]="'Green'" ></fudis-checkbox-group-option> </fudis-checkbox-group> ``` --- ### Radio Button Group **Selector:** `fudis-radio-button-group` | Input | Type | Default | Required | | ---------- | -------------------------------------- | ------- | -------- | | `control` | `FormControl` | — | **yes** | | `label` | `string` | — | **yes** | | `size` | `'sm' \| 'md' \| 'lg' \| 'full-width'` | `'lg'` | no | | `helpText` | `string` | — | no | | Output | Type | | -------------- | ------------------------------------------- | | `handleChange` | `EventEmitter<FudisRadioButtonChangeEvent>` | ```typescript colorControl = new FormControl<string | null>(null, [ FudisValidators.required('Select a color'), ]); colorOptions: FudisRadioButtonOption<object>[] = [ { value: 'red', label: 'Red' }, { value: 'blue', label: 'Blue' }, { value: 'green', label: 'Green' }, ]; ``` ```html <fudis-radio-button-group [label]="'Color'" [control]="colorControl"> @for (option of colorOptions; track option.value) { <fudis-radio-button [option]="option"></fudis-radio-button> } </fudis-radio-button-group> ``` --- ### Localized Text Group **Selector:** `fudis-localized-text-group` | Input | Type | Default | Required | | ----------- | ------------------------------------------- | --------------------------------------------------------------------------------------------- | -------- | | `formGroup` | `FormGroup` | — | **yes** | | `label` | `string` | — | **yes** | | `options` | `FudisLocalizedTextGroupFormGroupOptions[]` | `[{controlName:'fi',label:'FI'},{controlName:'sv',label:'SV'},{controlName:'en',label:'EN'}]` | no | | `variant` | `'text-input' \| 'text-area'` | `'text-input'` | no | | `size` | `'sm' \| 'md' \| 'lg'` | `'lg'` | no | | `helpText` | `string` | — | no | ```typescript localizedName = new FormGroup( { fi: new FormControl<string | null>(null), sv: new FormControl<string | null>(null), en: new FormControl<string | null>(null), }, [FudisGroupValidators.oneRequired("Provide at least one translation")], ); ``` ```html <fudis-localized-text-group [label]="'Course name'" [formGroup]="localizedName" ></fudis-localized-text-group> ``` --- ### Dialog **Selector:** `fudis-dialog` | Input | Type | Default | | ------ | -------------------------------------- | ------- | | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Open dialogs using `FudisDialogService.open(ComponentClass, options)` or `FudisDialogService.open(TemplateRef, options)`. Inside your dialog component: ```html <fudis-dialog [size]="'md'"> <fudis-heading fudisDialogTitle>Edit Item</fudis-heading> <fudis-dialog-content> <!-- Content here --> </fudis-dialog-content> <fudis-dialog-actions> <fudis-button fudisDialogClose [label]="'Cancel'" [variant]="'secondary'"></fudis-button> <fudis-button [label]="'Save'" (handleClick)="save()"></fudis-button> </fudis-dialog-actions> </fudis-dialog> ``` Or with a form inside: ```html <fudis-dialog [size]="'lg'"> <fudis-dialog-content> <fudis-form [title]="'Edit'" [level]="2"> <fudis-form-content> <!-- form fields --> </fudis-form-content> <fudis-form-actions> <fudis-button fudisDialogClose [label]="'Cancel'" [variant]="'secondary'"></fudis-button> <fudis-button fudisFormSubmit [label]="'Save'" [formValid]="form.valid"></fudis-button> </fudis-form-actions> </fudis-form> </fudis-dialog-content> </fudis-dialog> ``` --- ### Dropdown Menu **Selector:** `fudis-dropdown-menu` **Must be a direct child of `fudis-icon-button` with `[asMenuButton]="true"`.** | Input | Type | Default | | ------- | ------------------------------- | ---------- | | `align` | `'left' \| 'center' \| 'right'` | `'center'` | | `size` | `'sm' \| 'md' \| 'lg'` | `'lg'` | ```html <fudis-icon-button [ariaLabel]="'Actions'" [icon]="'three-dots'" [asMenuButton]="true"> <fudis-dropdown-menu [align]="'right'"> <fudis-dropdown-menu-item [label]="'Edit'" (handleClick)="edit()"></fudis-dropdown-menu-item> <fudis-dropdown-menu-item [label]="'Delete'" (handleClick)="delete()" ></fudis-dropdown-menu-item> </fudis-dropdown-menu> </fudis-icon-button> ``` Use `fudis-dropdown-menu-group` to group items with a label: ```html <fudis-dropdown-menu> <fudis-dropdown-menu-group [label]="'Actions'"> <fudis-dropdown-menu-item [label]="'Edit'" (handleClick)="edit()"></fudis-dropdown-menu-item> <fudis-dropdown-menu-item [label]="'Delete'" (handleClick)="delete()" ></fudis-dropdown-menu-item> </fudis-dropdown-menu-group> </fudis-dropdown-menu> ``` --- ### Notification **Selector:** `fudis-notification` | Input | Type | Default | | --------- | ---------------------------------------------- | ----------- | | `variant` | `'warning' \| 'danger' \| 'success' \| 'info'` | `'warning'` | ```html <fudis-notification [variant]="'success'"> <fudis-body-text>Your changes have been saved.</fudis-body-text> </fudis-notification> ``` --- ### Alert Group **Selector:** `fudis-alert-group` Alerts are managed via `FudisAlertService`. Place `fudis-alert-group` once in your app root. | Input | Type | Default | | -------------- | ----------------------------------- | --------- | | `position` | `'static' \| 'absolute' \| 'fixed'` | `'fixed'` | | `insideDialog` | `boolean` | — | ```typescript constructor(private alertService: FudisAlertService) {} showAlert() { this.alertService.addAlert({ message: of('Something went wrong'), type: 'danger', id: 'my-alert', }); } ``` ```html <!-- In app root template --> <fudis-alert-group></fudis-alert-group> ``` --- ### Badge **Selector:** `fudis-badge` | Input | Type | Default | | --------- | --------------------------------------------------------------- | ----------- | | `variant` | `'accent' \| 'danger' \| 'primary' \| 'secondary' \| 'success'` | `'primary'` | | `content` | `string` | — | ```html <fudis-badge [variant]="'success'" [content]="'Active'"></fudis-badge> ``` --- ### Description List **Selector:** `fudis-dl` Extends Grid — all Grid inputs available. | Input | Type | Default | | ------------- | ------------------------ | ----------- | | `variant` | `'regular' \| 'compact'` | `'regular'` | | `disableGrid` | `boolean` | `false` | | `tag` | `'dl' \| 'p'` | `'dl'` | ```html <fudis-dl [variant]="'regular'" [columns]="{ md: '1fr 1fr' }"> <fudis-dl-item> <fudis-dt>Name</fudis-dt> <fudis-dd>John Doe</fudis-dd> </fudis-dl-item> <fudis-dl-item> <fudis-dt>Email</fudis-dt> <fudis-dd>john@example.com</fudis-dd> </fudis-dl-item> </fudis-dl> ``` --- ### Breadcrumbs **Selector:** `fudis-breadcrumbs` | Input | Type | Default | Required | | ------- | -------- | ------- | -------- | | `label` | `string` | — | **yes** | ```html <fudis-breadcrumbs [label]="'Breadcrumbs navigation'"> @for (link of links; track link.url; let index = $index) { <fudis-breadcrumbs-item> @if (index + 1 !== links.length) { <a [href]="link.url">{{ link.label }}</a> } @if (index + 1 === links.length) { <fudis-body-text>{{ link.label }}</fudis-body-text> } </fudis-breadcrumbs-item> } </fudis-breadcrumbs> ``` --- ### Tab Navigation Uses three components together: **`fudis-tab-navigation-bar`** — selector for the tab bar container | Input | Type | Default | | --------- | ----------------------------- | ----------- | | `panel` | `TabNavigationPanelComponent` | — | | `variant` | `'primary' \| 'secondary'` | `'primary'` | | `id` | `string` | — | **`fudis-tab-navigation-tab`** — inline attribute selector on `<a>` or `<button>` | Input | Type | Default | | -------- | --------- | ------- | | `id` | `string` | — | | `active` | `boolean` | `false` | **`fudis-tab-navigation-panel`** — content panel selector | Input | Type | | ----- | -------- | | `id` | `string` | ```html <fudis-tab-navigation-bar [panel]="panel"> <a fudis-tab-navigation-tab [id]="'tab-1'" [active]="activeTab === 1" (click)="activeTab = 1" >Tab 1</a > <a fudis-tab-navigation-tab [id]="'tab-2'" [active]="activeTab === 2" (click)="activeTab = 2" >Tab 2</a > </fudis-tab-navigation-bar> <fudis-tab-navigation-panel #panel [id]="'panel'"> @if (activeTab === 1) { <p>Tab 1 content</p> } @if (activeTab === 2) { <p>Tab 2 content</p> } </fudis-tab-navigation-panel> ``` --- ### Pagination **Selector:** `fudis-pagination` | Input | Type | Default | Required | | ----------------------- | --------- | ------- | -------- | | `paginationAriaLabel` | `string` | — | **yes** | | `pageCount` | `number` | — | **yes** | | `pageIndex` | `number` | `0` | no | | `autoFocusOnPageChange` | `boolean` | `true` | no | | Output | Type | | ------------ | ---------------------- | | `pageChange` | `EventEmitter<number>` | ```html <fudis-pagination [paginationAriaLabel]="'Search results'" [pageCount]="totalPages" [pageIndex]="currentPage" (pageChange)="currentPage = $event" ></fudis-pagination> ``` --- ### Loading Spinner **Selector:** `fudis-loading-spinner` | Input | Type | Default | | --------------- | ---------------- | ----------------------- | | `label` | `string` | Fudis default 'Loading' | | `variant` | `'sm' \| 'lg'` | `'sm'` | | `statusMessage` | `string \| null` | Fudis defaults | | `visible` | `boolean` | `true` | ```html <fudis-loading-spinner [variant]="'lg'" [visible]="isLoading"></fudis-loading-spinner> ``` --- ### Footer **Selector:** `fudis-footer` No inputs. Content projected. Displays Funidata logo automatically. ```html <fudis-footer> <a fudisLink href="#" [external]="true" [title]="'Example link'"></a> <p>Custom footer content</p> </fudis-footer> ``` --- ### Horizontal Rule **Selector:** `fudis-hr` No inputs. Renders a styled `<hr>`. ```html <fudis-hr></fudis-hr> ``` --- ### Language Badge Group **Selector:** `fudis-language-badge-group` | Input | Type | Default | Required | | --------------------- | -------------------------- | ------- | -------- | | `translatedLanguages` | `('fi' \| 'sv' \| 'en')[]` | — | **yes** | | Output | Type | | ------------- | ----------------------------------------- | | `handleClick` | `EventEmitter<FudisLanguageAbbr \| null>` | ```html <fudis-language-badge-group [translatedLanguages]="['fi', 'en']" (handleClick)="onLanguageChange($event)" ></fudis-language-badge-group> ``` --- ## Directives ### Content Directives (used with `ng-template`) | Directive | Parent Component | | ------------------------ | ------------------------------------- | | `fudisExpandableContent` | `fudis-expandable` | | `fudisDialogTitle` | `fudis-dialog` | | `fudisSelectOptions` | `fudis-select` or `fudis-multiselect` | ### Content Directives (used with HTML selector) | Directive | Parent Component | | -------------------------- | ------------------ | | `fudis-form-content` | `fudis-form` | | `fudis-form-actions` | `fudis-form` | | `fudis-form-header` | `fudis-form` | | `fudis-fieldset-content` | `fudis-fieldset` | | `fudis-fieldset-actions` | `fudis-fieldset` | | `fudis-expandable-actions` | `fudis-expandable` | | `fudis-section-content` | `fudis-section` | | `fudis-section-actions` | `fudis-section` | | `fudis-dialog-content` | `fudis-dialog` | | `fudis-dialog-actions` | `fudis-dialog` | ### Behavioral Directives | Directive | Selector | Description | | ----------------------- | -------------------- | --------------------------------------------------- | | `LinkDirective` | `fudisLink` | Adds link styling + external link handling to `<a>` | | `PopoverDirective` | `fudisPopover` | Adds popover on click to any element | | `FormSubmitDirective` | `fudisFormSubmit` | On `fudis-button`, triggers error summary on click | | `DialogCloseDirective` | `fudisDialogClose` | On `fudis-button`, closes the dialog | | `GridDirective` | `fudisGrid` | Adds grid layout to any element | | `GridItemDirective` | `fudisGridItem` | Adds grid item behavior to any element | | `DateStartDirective` | `fudisDateStart` | Marks datepicker as range start | | `DateEndDirective` | `fudisDateEnd` | Marks datepicker as range end | | `ErrorMessageDirective` | Custom error display | For custom error messages on form controls | ### Link Directive ```html <a fudisLink [title]="'Funidata'" href="https://funidata.fi" [external]="true">Funidata</a> <a fudisLink [title]="'Home'" routerLink="/">Home</a> ``` | Input | Type | Default | Required | | -------------- | --------------------------- | ------- | -------- | | `title` | `string` | — | **yes** | | `external` | `boolean` | `false` | no | | `size` | `'inherit' \| 'md' \| 'lg'` | `'md'` | no | | `initialFocus` | `boolean` | `false` | no | --- ## Services | Service | Purpose | | -------------------------- | ---------------------------------------------------------------------------- | | `FudisDialogService` | Open/close dialogs: `dialogService.open(Component)`, `dialogService.close()` | | `FudisAlertService` | Add/dismiss alerts: `alertService.addAlert({message, type, id})` | | `FudisTranslationService` | Set language and custom translations | | `FudisErrorSummaryService` | Programmatic control of error summary | | `FudisGridService` | Set default grid properties app-wide | | `FudisBreakpointService` | Observe viewport breakpoint changes | --- ## Type Quick Reference ```typescript // Button type FudisButtonVariant = 'primary' | 'secondary' | 'tertiary'; type FudisButtonSize = 'extra-small' | 'small' | 'medium'; type FudisButtonType = 'submit' | 'button'; // Typography type FudisHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6; type FudisHeadingVariant = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; type FudisBodyText = 'lg-regular' | 'md-regular' | 'sm-regular' | 'lg-light' | 'md-light'; type FudisTextAlign = 'left' | 'right' | 'center'; // Forms type FudisInputSize = 'sm' | 'md' | 'lg'; type FudisInputType = 'email' | 'number' | 'password' | 'tel' | 'text' | 'url'; type FudisSelectVariant = 'dropdown' | 'autocompleteDropdown' | 'autocompleteType'; type FudisSelectOption<T = string> = { value: T; label: string; subLabel?: string; disabled?: boolean }; // Layout type FudisGridWidth = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs' | 'initial' | 'full-width'; type FudisGridAlign = 'start' | 'end' | 'center'; type FudisSpacing = 'none' | 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; type FudisBreakpointKey = 'default' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; // Misc type FudisNotification = 'warning' | 'danger' | 'success' | 'info'; type FudisBadgeVariant = 'accent' | 'danger' | 'primary' | 'secondary' | 'success'; type FudisDialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; type FudisLanguageAbbr = 'fi' | 'sv' | 'en'; type FudisPopoverPosition = 'left' | 'right' | 'above' | 'below'; type FudisIconColor = 'yellow' | 'red' | 'gray-dark' | 'gray-light' | 'primary' | 'primary-dark' | 'green' | 'white'; type FudisIconRotate = 'flip-180' | 'cw-90' | 'ccw-90' | 'none'; // Icons (partial list — most common) type FudisIcon = 'search' | 'edit' | 'delete' | 'close' | 'check' | 'plus' | 'minus' | 'alert' | 'info' | 'chevron' | 'arrow-solid' | 'calendar' | 'three-dots' | 'settings' | 'person' | 'mail' | 'new-tab' | 'back' | 'menu' | ... ; // 90+ icons total ```