md-transfer-list spec card
Identity · when to use / when NOT · decision cues · behavioural contract · do/don't · anti-patterns · full API. Paste into your agent when you're implementing with this component.
A dual-list selector. Two columns — source and target — with per-side
search, select-all headers, checkbox rows, and four movers between them. The
value prop is the set of item values currently in the target column.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-transfer-list
id="roles"
source-title="Available roles"
target-title="Assigned roles"
style="--md-transfer-list-height: 340px;"
></md-transfer-list>
<script type="module">
const el = document.getElementById('roles');
// Arrays are PROPERTIES, never attributes.
el.items = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payment methods' },
{ value: 'owner', label: 'Owner', disabled: true },
];
el.value = ['support']; // 'support' starts in the TARGET column
el.addEventListener('mdChange', (e) => saveRoles(e.detail));
</script>import { useState } from 'react';
import { MdTransferList } from '@awc-ui/react';
const ROLES = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payment methods' },
{ value: 'owner', label: 'Owner', disabled: true },
];
export function RoleTransfer() {
const [value, setValue] = useState(['support']);
return (
<MdTransferList
sourceTitle="Available roles"
targetTitle="Assigned roles"
items={ROLES}
value={value}
onMdChange={(e) => setValue(e.detail)}
style={{ '--md-transfer-list-height': '340px' }}
/>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({ imports: [AwcUiModule], schemas: [CUSTOM_ELEMENTS_SCHEMA] })
export class AppModule {}
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-roles',
template: `
<md-transfer-list
source-title="Available roles"
target-title="Assigned roles"
[items]="roles"
[value]="assigned"
(mdChange)="assigned = $event.detail"
style="--md-transfer-list-height: 340px;"
></md-transfer-list>
`,
})
export class RolesComponent {
roles = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payment methods' },
{ value: 'owner', label: 'Owner', disabled: true },
];
assigned = ['support'];
}<script setup lang="ts">
import { ref } from 'vue';
import '@awc-ui/core/define';
const roles = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payment methods' },
{ value: 'owner', label: 'Owner', disabled: true },
];
const assigned = ref(['support']);
</script>
<template>
<md-transfer-list
source-title="Available roles"
target-title="Assigned roles"
:items="roles"
:value="assigned"
@mdChange="assigned = $event.detail"
style="--md-transfer-list-height: 340px;"
/>
</template><script lang="ts">
import '@awc-ui/core/define';
const roles = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payment methods' },
{ value: 'owner', label: 'Owner', disabled: true },
];
let assigned = ['support'];
</script>
<md-transfer-list
source-title="Available roles"
target-title="Assigned roles"
items={roles}
value={assigned}
on:mdChange={(e) => (assigned = e.detail)}
style="--md-transfer-list-height: 340px;"
></md-transfer-list>
Already installed? See the
Installation guide for one-time package setup
(core + tokens, fonts). Each tab below shows two patterns for using
md-transfer-list in your project: Option A registers every AWC UI
component at once (simplest), Option B imports only this component for
tree-shake-friendly bundles.
<!-- ─── Option A: global registration (all components) ─── -->
<script type="module">
import '@awc-ui/core/define';
</script>
<!-- ─── Option B: single import (tree-shake only md-transfer-list) ─── -->
<script type="module">
import '@awc-ui/core/components/md-transfer-list';
</script>
<md-transfer-list></md-transfer-list>// ─── Option A: typed React wrapper (registers all components) ───
// Importing from '@awc-ui/react' calls defineCustomElements() as a
// side effect, so every md-* element becomes available in the browser.
import { MdTransferList } from '@awc-ui/react';
export function Example() {
return <MdTransferList></MdTransferList>;
}
// ─── Option B: single import (tree-shake to only md-transfer-list) ───
// Skip the wrapper and use the raw custom element. Smallest bundle,
// but you lose typed props/events on JSX.
import '@awc-ui/core/components/md-transfer-list';
export function ExampleTreeShaken() {
return <md-transfer-list></md-transfer-list>;
}// ─── Option A: schema module (any md-* element accepted) ───
// Pair with `defineCustomElements(window)` in main.ts.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@Component({
standalone: true,
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<md-transfer-list></md-transfer-list>`,
})
export class ExampleComponent {}
// ─── Option B: typed directive (tree-shake friendly) ───
// Pair with `import '@awc-ui/core/components/md-transfer-list'` in main.ts.
import { Component } from '@angular/core';
import { MdTransferList } from '@awc-ui/angular';
@Component({
standalone: true,
imports: [MdTransferList],
template: `<md-transfer-list></md-transfer-list>`,
})
export class ExampleTreeShakenComponent {}<!-- ─── Option A: typed Vue wrapper (registers all components) ─── -->
<script setup lang="ts">
import { MdTransferList } from '@awc-ui/vue';
</script>
<template>
<MdTransferList></MdTransferList>
</template>
<!-- ─── Option B: single import (tree-shake to only md-transfer-list) ─── -->
<script setup lang="ts">
import '@awc-ui/core/components/md-transfer-list';
</script>
<template>
<md-transfer-list></md-transfer-list>
</template><!-- ─── Option A: global registration (done once in main entry) ─── -->
<!-- main.ts: -->
<!-- import { defineCustomElements } from '@awc-ui/svelte'; -->
<!-- defineCustomElements(window); -->
<md-transfer-list></md-transfer-list>
<!-- ─── Option B: single import (tree-shake to only md-transfer-list) ─── -->
<script lang="ts">
import '@awc-ui/core/components/md-transfer-list';
</script>
<md-transfer-list></md-transfer-list>| Situation | Use instead |
|---|---|
| Picking a handful from a short list (< ~10) | md-multi-select |
| Picking exactly one | md-select |
| Free-text entry with suggestions | md-autocomplete |
| A flat multi-select where order/side doesn’t matter | md-checkbox group or md-list |
| Toggling a handful of independent options | md-switch / md-checkbox |
| The two sides mean different things (not the same items relocated) | Two separate lists |
| Narrow mobile viewports | md-multi-select — three columns don’t fit |
value is the target column. Anything in items but not in value renders
on the source side. There is no separate “source items” prop — the component
derives the source side by subtraction.
<md-transfer-list id="roles"
source-title="Source (items minus value)"
target-title="Target (value)"
></md-transfer-list>
<script type="module">
const el = document.getElementById('roles');
el.items = [
{ value: 'a', label: 'Analytics', description: 'Read dashboards' },
{ value: 'b', label: 'Billing', disabled: true },
{ value: 'c', label: 'Content editor' },
];
el.value = ['b']; // 'b' starts in the TARGET column
</script>import { useState } from 'react';
import { MdTransferList } from '@awc-ui/react';
const ITEMS = [
{ value: 'a', label: 'Analytics', description: 'Read dashboards' },
{ value: 'b', label: 'Billing', disabled: true },
{ value: 'c', label: 'Content editor' },
];
export function Roles() {
// 'b' starts in the TARGET column; the source side is ITEMS minus value.
const [value, setValue] = useState(['b']);
return (
<MdTransferList
sourceTitle="Source (items minus value)"
targetTitle="Target (value)"
items={ITEMS}
value={value}
onMdChange={(e) => setValue(e.detail)}
/>
);
}import { Component } from '@angular/core';
@Component({
selector: 'app-roles',
template: `
<md-transfer-list
source-title="Source (items minus value)"
target-title="Target (value)"
[items]="items"
[value]="value"
(mdChange)="value = $event.detail"
></md-transfer-list>
`,
})
export class RolesComponent {
items = [
{ value: 'a', label: 'Analytics', description: 'Read dashboards' },
{ value: 'b', label: 'Billing', disabled: true },
{ value: 'c', label: 'Content editor' },
];
value = ['b']; // starts in the TARGET column
}<script setup lang="ts">
import { ref } from 'vue';
import '@awc-ui/core/define';
const items = [
{ value: 'a', label: 'Analytics', description: 'Read dashboards' },
{ value: 'b', label: 'Billing', disabled: true },
{ value: 'c', label: 'Content editor' },
];
const value = ref(['b']); // starts in the TARGET column
</script>
<template>
<md-transfer-list
source-title="Source (items minus value)"
target-title="Target (value)"
:items="items"
:value="value"
@mdChange="value = $event.detail"
/>
</template><script lang="ts">
import '@awc-ui/core/define';
const items = [
{ value: 'a', label: 'Analytics', description: 'Read dashboards' },
{ value: 'b', label: 'Billing', disabled: true },
{ value: 'c', label: 'Content editor' },
];
let value = ['b']; // starts in the TARGET column
</script>
<md-transfer-list
source-title="Source (items minus value)"
target-title="Target (value)"
{items}
{value}
on:mdChange={(e) => (value = e.detail)}
></md-transfer-list>| Mover | Acts on |
|---|---|
› | Items checked and currently visible under that side’s search filter |
‹ | Same, in the other direction |
» | Every non-disabled item on that side — ignores the search filter |
« | Same, in the other direction |
Check state is tracked per side and is cleared for that side after a move.
disabled items never move, in single-step or bulk.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-transfer-list
id="columns"
source-title="Columns"
target-title="Shown in report"
single-step-only
show-select-all="false"
></md-transfer-list>
<script type="module">
var el = document.getElementById('columns');
el.items = [
{ value: 'name', label: 'Name' },
{ value: 'email', label: 'Email' },
{ value: 'role', label: 'Role' },
{ value: 'created', label: 'Created at' },
{ value: 'last', label: 'Last seen' },
{ value: 'plan', label: 'Plan' },
];
el.value = ['name', 'email'];
</script>import { MdTransferList } from '@awc-ui/react';
const items = [
{ value: 'name', label: 'Name' },
{ value: 'email', label: 'Email' },
{ value: 'role', label: 'Role' },
{ value: 'created', label: 'Created at' },
{ value: 'last', label: 'Last seen' },
{ value: 'plan', label: 'Plan' },
];
const value = ['name', 'email'];
export function Demo() {
return (
<>
<MdTransferList
id="columns"
items={items}
value={value}
sourceTitle="Columns"
targetTitle="Shown in report"
singleStepOnly
showSelectAll="false"
></MdTransferList>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts — the bound values live on the class
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent {
items = [
{ value: 'name', label: 'Name' },
{ value: 'email', label: 'Email' },
{ value: 'role', label: 'Role' },
{ value: 'created', label: 'Created at' },
{ value: 'last', label: 'Last seen' },
{ value: 'plan', label: 'Plan' },
];
value = ['name', 'email'];
}
<!-- app.component.html -->
<md-transfer-list
id="columns"
[items]="items"
[value]="value"
source-title="Columns"
target-title="Shown in report"
single-step-only
show-select-all="false"
></md-transfer-list><script setup>
import '@awc-ui/core/define';
const items = [
{ value: 'name', label: 'Name' },
{ value: 'email', label: 'Email' },
{ value: 'role', label: 'Role' },
{ value: 'created', label: 'Created at' },
{ value: 'last', label: 'Last seen' },
{ value: 'plan', label: 'Plan' },
];
const value = ['name', 'email'];
</script>
<template>
<md-transfer-list
id="columns"
:items="items"
:value="value"
source-title="Columns"
target-title="Shown in report"
single-step-only
show-select-all="false"
></md-transfer-list>
</template><script>
import '@awc-ui/core/define';
const items = [
{ value: 'name', label: 'Name' },
{ value: 'email', label: 'Email' },
{ value: 'role', label: 'Role' },
{ value: 'created', label: 'Created at' },
{ value: 'last', label: 'Last seen' },
{ value: 'plan', label: 'Plan' },
];
const value = ['name', 'email'];
</script>
<md-transfer-list
id="columns"
{items}
{value}
source-title="Columns"
target-title="Shown in report"
single-step-only
show-select-all="false"
></md-transfer-list>searchable defaults to true. Turn it off for short lists where a search
field is noise. density="-1…-4" tightens row padding and is forwarded to both
search fields.
<!-- index.html <head> — the icon font the components draw from -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap">
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-transfer-list
id="tags"
source-title="Tags" target-title="Applied"
density="-2"
source-search-placeholder="Search tags"
target-search-placeholder="Search applied"
count-template="{checked}/{total} selected"
></md-transfer-list>
<md-transfer-list
source-title="Short list" target-title="Picked"
searchable="false"
empty-icon="inbox" empty-text="Nothing here yet"
></md-transfer-list>
<script type="module">
var el = document.getElementById('tags');
el.items = [
{ value: 'urgent', label: 'Urgent' },
{ value: 'billing', label: 'Billing' },
{ value: 'bug', label: 'Bug' },
{ value: 'feature', label: 'Feature request' },
];
el.value = ['urgent'];
</script>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { MdTransferList } from '@awc-ui/react';
const items = [
{ value: 'urgent', label: 'Urgent' },
{ value: 'billing', label: 'Billing' },
{ value: 'bug', label: 'Bug' },
{ value: 'feature', label: 'Feature request' },
];
const value = ['urgent'];
export function Demo() {
return (
<>
<MdTransferList
id="tags"
items={items}
value={value}
sourceTitle="Tags" targetTitle="Applied"
density="-2"
sourceSearchPlaceholder="Search tags"
targetSearchPlaceholder="Search applied"
countTemplate="{checked}/{total} selected"
></MdTransferList>
<MdTransferList
sourceTitle="Short list" targetTitle="Picked"
searchable="false"
emptyIcon="inbox" emptyText="Nothing here yet"
></MdTransferList>
</>
);
}// Icons need the Material Symbols stylesheet in index.html — see Installation.
// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts — the bound values live on the class
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent {
items = [
{ value: 'urgent', label: 'Urgent' },
{ value: 'billing', label: 'Billing' },
{ value: 'bug', label: 'Bug' },
{ value: 'feature', label: 'Feature request' },
];
value = ['urgent'];
}
<!-- app.component.html -->
<md-transfer-list
id="tags"
[items]="items"
[value]="value"
source-title="Tags" target-title="Applied"
density="-2"
source-search-placeholder="Search tags"
target-search-placeholder="Search applied"
count-template="{checked}/{total} selected"
></md-transfer-list>
<md-transfer-list
source-title="Short list" target-title="Picked"
searchable="false"
empty-icon="inbox" empty-text="Nothing here yet"
></md-transfer-list><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
const items = [
{ value: 'urgent', label: 'Urgent' },
{ value: 'billing', label: 'Billing' },
{ value: 'bug', label: 'Bug' },
{ value: 'feature', label: 'Feature request' },
];
const value = ['urgent'];
</script>
<template>
<md-transfer-list
id="tags"
:items="items"
:value="value"
source-title="Tags" target-title="Applied"
density="-2"
source-search-placeholder="Search tags"
target-search-placeholder="Search applied"
count-template="{checked}/{total} selected"
></md-transfer-list>
<md-transfer-list
source-title="Short list" target-title="Picked"
searchable="false"
empty-icon="inbox" empty-text="Nothing here yet"
></md-transfer-list>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
const items = [
{ value: 'urgent', label: 'Urgent' },
{ value: 'billing', label: 'Billing' },
{ value: 'bug', label: 'Bug' },
{ value: 'feature', label: 'Feature request' },
];
const value = ['urgent'];
</script>
<md-transfer-list
id="tags"
{items}
{value}
source-title="Tags" target-title="Applied"
density="-2"
source-search-placeholder="Search tags"
target-search-placeholder="Search applied"
count-template="{checked}/{total} selected"
></md-transfer-list>
<md-transfer-list
source-title="Short list" target-title="Picked"
searchable="false"
empty-icon="inbox" empty-text="Nothing here yet"
></md-transfer-list>count-template interpolates {checked} and {total} — keep both tokens when
translating.
| Event | Cancelable | Detail | Fires |
|---|---|---|---|
mdChange | no | string[] — the new target set | Every change to value |
mdMove | no | { direction: 'left' | 'right', moved: string[], target: string[] } | After a move, describing it |
Methods — moveSelectedRight(), moveSelectedLeft().
<md-transfer-list
id="roles"
source-title="Available roles"
target-title="Assigned roles"
density="-1"
></md-transfer-list>
<script type="module">
const el = document.getElementById('roles');
const ROLES = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
el.items = ROLES;
el.value = ['support']; // starts in the TARGET column
el.addEventListener('mdChange', (e) => saveRoles(e.detail));
el.addEventListener('mdMove', (e) => {
const { direction, moved } = e.detail;
toast(moved.length + ' moved ' + (direction === 'right' ? 'in' : 'out'));
});
</script>import { useState } from 'react';
import { MdTransferList } from '@awc-ui/react';
const ROLES = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
export function RoleAssignment() {
const [assigned, setAssigned] = useState<string[]>(['support']);
// items and value are forwarded as PROPERTIES by the @awc-ui/react wrapper,
// so they take arrays directly — no ref, no effect.
return (
<MdTransferList
sourceTitle="Available roles"
targetTitle="Assigned roles"
density="-1"
items={ROLES}
value={assigned}
onMdChange={(e) => setAssigned(e.detail)}
onMdMove={(e) => console.log(e.detail.direction, e.detail.moved)}
/>
);
}import { Component } from '@angular/core';
@Component({
selector: 'app-role-assignment',
template: `
<md-transfer-list
source-title="Available roles"
target-title="Assigned roles"
density="-1"
[items]="roles"
[value]="assigned"
(mdChange)="assigned = $event.detail"
(mdMove)="onMove($event)"
></md-transfer-list>
`,
})
export class RoleAssignmentComponent {
roles = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
assigned = ['support'];
onMove(e: CustomEvent<{ direction: 'left' | 'right'; moved: string[] }>) {
console.log(e.detail.direction, e.detail.moved);
}
}<script setup lang="ts">
import { ref } from 'vue';
import '@awc-ui/core/define';
const roles = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
const assigned = ref(['support']);
</script>
<template>
<md-transfer-list
source-title="Available roles"
target-title="Assigned roles"
density="-1"
:items="roles"
:value="assigned"
@mdChange="assigned = $event.detail"
@mdMove="console.log($event.detail.direction, $event.detail.moved)"
/>
</template><script lang="ts">
import '@awc-ui/core/define';
const roles = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
let assigned = ['support'];
</script>
<md-transfer-list
source-title="Available roles"
target-title="Assigned roles"
density="-1"
items={roles}
value={assigned}
on:mdChange={(e) => (assigned = e.detail)}
on:mdMove={(e) => console.log(e.detail.direction, e.detail.moved)}
></md-transfer-list>| Property | Attribute | Type | Default | Reflects |
|---|---|---|---|---|
items | items | MdTransferListItem[] | string | [] | — |
value | JS only | string[] | [] | — |
sourceTitle | source-title | string | 'Choices' | — |
targetTitle | target-title | string | 'Chosen' | — |
searchable | searchable | boolean | true | Yes |
sourceSearchPlaceholder | source-search-placeholder | string | 'Search choices' | — |
targetSearchPlaceholder | target-search-placeholder | string | 'Search chosen' | — |
showSelectAll | show-select-all | boolean | true | Yes |
searchIcon | search-icon | string | 'search' | — |
moveRightIcon | move-right-icon | string | 'chevron_right' | — |
moveLeftIcon | move-left-icon | string | 'chevron_left' | — |
moveAllRightIcon | move-all-right-icon | string | 'keyboard_double_arrow_right' | — |
moveAllLeftIcon | move-all-left-icon | string | 'keyboard_double_arrow_left' | — |
emptyIcon | empty-icon | string | '' | — |
emptyText | empty-text | string | 'No items' | — |
countTemplate | count-template | string | '{checked}/{total} selected' | — |
density | density | 0 | -1 | -2 | -3 | -4 | 0 | Yes |
disabled | disabled | boolean | false | Yes |
singleStepOnly | single-step-only | boolean | false | Yes |
moveRightLabel | move-right-label | string | 'Move selected to target' | — |
moveLeftLabel | move-left-label | string | 'Move selected to source' | — |
moveAllRightLabel | move-all-right-label | string | 'Move all to target' | — |
moveAllLeftLabel | move-all-left-label | string | 'Move all to source' | — |
| Method | Parameters |
|---|---|
moveSelectedRight() | none |
moveSelectedLeft() | none |
Override on the host element for per-instance theming:
| Property | Description |
|---|---|
--md-transfer-list-width | Explicit inline-size (default: auto; 100% supported) |
--md-transfer-list-min-width | Minimum inline-size |
--md-transfer-list-max-width | Maximum inline-size |
--md-transfer-list-height | Explicit block-size (default: auto; 100% supported — |
--md-transfer-list-min-height | Minimum block-size |
--md-transfer-list-max-height | Maximum block-size |
--md-transfer-list-column-min-width | Min-width of each column |
--md-transfer-list-column-max-block | Max-height of each column (default 360px, |
--md-transfer-list-container-shape | Container corner radius |
--md-transfer-list-container-color | Column background |
--md-transfer-list-header-color | Header background |
--md-transfer-list-header-text-color | Header text color |
--md-transfer-list-item-inset | Row plate inset from the panel edge |
--md-transfer-list-item-padding | Padding of each row (derived from |
--md-transfer-list-item-gap | Gap between checkbox and label |
--md-transfer-list-divider-color | Divider color |
--md-transfer-list-controls-gap | Gap between controls buttons |
--md-transfer-list-empty-icon-size | Empty-state icon size (default 32px, |
--md-transfer-list-control-size | — |
--md-transfer-list-control-icon-size | — |
Style internal elements through shadow DOM with ::part():
| Part | Description |
|---|---|
header | Header bar |
header-title | Header title cluster |
count | "{n}/{total} selected" pill |
search | Search field per side |
empty | Empty-state message |
empty-icon | Optional empty-state glyph (via `empty-icon` prop) |
list | The <ul> listbox per side |
item | Each <li> row |
controls | Centre column with move buttons |
role="group" with aria-disabled. Each list is a
role="listbox" with aria-multiselectable="true", labelled by its column
title; rows are role="option" with aria-selected / aria-disabled.Tab to a row, Space or Enter to toggle.md-checkbox per row is inert + aria-hidden — the row itself
is the control, which avoids a nested interactive element.move-*-label props.
Note these are deliberately not aria-*-prefixed attributes: a custom
attribute in the ARIA namespace fails axe’s aria-valid-attr.role="status", so filtering down to nothing is announced.disabled, the scroll regions keep tabindex="0"
so they stay reachable (axe scrollable-region-focusable).<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-transfer-list id="locked" source-title="Available" target-title="Assigned" disabled></md-transfer-list>
<script type="module">
var el = document.getElementById('locked');
el.items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
el.value = ['support'];
</script>import { MdTransferList } from '@awc-ui/react';
const items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
const value = ['support'];
export function Demo() {
return (
<>
<MdTransferList id="locked"
items={items}
value={value} sourceTitle="Available" targetTitle="Assigned" disabled></MdTransferList>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts — the bound values live on the class
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent {
items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
value = ['support'];
}
<!-- app.component.html -->
<md-transfer-list id="locked"
[items]="items"
[value]="value" source-title="Available" target-title="Assigned" disabled></md-transfer-list><script setup>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
const value = ['support'];
</script>
<template>
<md-transfer-list id="locked"
:items="items"
:value="value" source-title="Available" target-title="Assigned" disabled></md-transfer-list>
</template><script>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
const value = ['support'];
</script>
<md-transfer-list id="locked"
{items}
{value} source-title="Available" target-title="Assigned" disabled></md-transfer-list>RTL — layout is logical-property based, so the source/target columns swap
under dir="rtl". The mover glyphs do not auto-mirror: pass mirrored values
yourself. See RTL.
<!-- index.html <head> — the icon font the components draw from -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap">
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div dir="rtl">
<md-transfer-list
id="tl-rtl"
dir="rtl"
source-title="الأدوار المتاحة"
target-title="الأدوار المُسندة"
source-search-placeholder="بحث"
target-search-placeholder="بحث"
move-right-icon="chevron_left"
move-left-icon="chevron_right"
move-all-right-icon="keyboard_double_arrow_left"
move-all-left-icon="keyboard_double_arrow_right"
style="inline-size: 100%; --md-transfer-list-height: 260px; --md-transfer-list-column-min-width: 180px;"
></md-transfer-list>
</div>
<script type="module">
var el = document.getElementById('tl-rtl');
el.items = [
{ value: 'analytics', label: 'التحليلات' },
{ value: 'billing', label: 'الفوترة' },
{ value: 'content', label: 'المحتوى' },
{ value: 'support', label: 'الدعم' },
];
el.value = ['support'];
</script>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { MdTransferList } from '@awc-ui/react';
const items = [
{ value: 'analytics', label: 'التحليلات' },
{ value: 'billing', label: 'الفوترة' },
{ value: 'content', label: 'المحتوى' },
{ value: 'support', label: 'الدعم' },
];
const value = ['support'];
export function Demo() {
return (
<>
<div dir="rtl">
<MdTransferList
id="tl-rtl"
items={items}
value={value}
dir="rtl"
sourceTitle="الأدوار المتاحة"
targetTitle="الأدوار المُسندة"
sourceSearchPlaceholder="بحث"
targetSearchPlaceholder="بحث"
moveRightIcon="chevron_left"
moveLeftIcon="chevron_right"
moveAllRightIcon="keyboard_double_arrow_left"
moveAllLeftIcon="keyboard_double_arrow_right"
style={{ inlineSize: '100%', '--md-transfer-list-height': '260px', '--md-transfer-list-column-min-width': '180px' }}
></MdTransferList>
</div>
</>
);
}// Icons need the Material Symbols stylesheet in index.html — see Installation.
// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts — the bound values live on the class
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent {
items = [
{ value: 'analytics', label: 'التحليلات' },
{ value: 'billing', label: 'الفوترة' },
{ value: 'content', label: 'المحتوى' },
{ value: 'support', label: 'الدعم' },
];
value = ['support'];
}
<!-- app.component.html -->
<div dir="rtl">
<md-transfer-list
id="tl-rtl"
[items]="items"
[value]="value"
dir="rtl"
source-title="الأدوار المتاحة"
target-title="الأدوار المُسندة"
source-search-placeholder="بحث"
target-search-placeholder="بحث"
move-right-icon="chevron_left"
move-left-icon="chevron_right"
move-all-right-icon="keyboard_double_arrow_left"
move-all-left-icon="keyboard_double_arrow_right"
style="inline-size: 100%; --md-transfer-list-height: 260px; --md-transfer-list-column-min-width: 180px;"
></md-transfer-list>
</div><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'التحليلات' },
{ value: 'billing', label: 'الفوترة' },
{ value: 'content', label: 'المحتوى' },
{ value: 'support', label: 'الدعم' },
];
const value = ['support'];
</script>
<template>
<div dir="rtl">
<md-transfer-list
id="tl-rtl"
:items="items"
:value="value"
dir="rtl"
source-title="الأدوار المتاحة"
target-title="الأدوار المُسندة"
source-search-placeholder="بحث"
target-search-placeholder="بحث"
move-right-icon="chevron_left"
move-left-icon="chevron_right"
move-all-right-icon="keyboard_double_arrow_left"
move-all-left-icon="keyboard_double_arrow_right"
style="inline-size: 100%; --md-transfer-list-height: 260px; --md-transfer-list-column-min-width: 180px;"
></md-transfer-list>
</div>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'التحليلات' },
{ value: 'billing', label: 'الفوترة' },
{ value: 'content', label: 'المحتوى' },
{ value: 'support', label: 'الدعم' },
];
const value = ['support'];
</script>
<div dir="rtl">
<md-transfer-list
id="tl-rtl"
{items}
{value}
dir="rtl"
source-title="الأدوار المتاحة"
target-title="الأدوار المُسندة"
source-search-placeholder="بحث"
target-search-placeholder="بحث"
move-right-icon="chevron_left"
move-left-icon="chevron_right"
move-all-right-icon="keyboard_double_arrow_left"
move-all-left-icon="keyboard_double_arrow_right"
style="inline-size: 100%; --md-transfer-list-height: 260px; --md-transfer-list-column-min-width: 180px;"
></md-transfer-list>
</div>Density — density="-1…-4" tightens row padding and is forwarded to the two
search fields. See Density.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-transfer-list density="0" source-title="Roles" target-title="Assigned"></md-transfer-list>
<md-transfer-list density="-1" source-title="Roles" target-title="Assigned"></md-transfer-list>
<md-transfer-list density="-2" source-title="Roles" target-title="Assigned"></md-transfer-list>
<md-transfer-list density="-3" source-title="Roles" target-title="Assigned"></md-transfer-list>
<md-transfer-list density="-4" source-title="Roles" target-title="Assigned"></md-transfer-list>
<script type="module">
// Plain HTML has nowhere to put an array, so items / value are assigned as
// properties. Every framework below binds them directly instead.
for (const tl of document.querySelectorAll('md-transfer-list')) {
tl.items = roles;
tl.value = ['support'];
}
</script>import { MdTransferList } from '@awc-ui/react';
const ROLES = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'content', label: 'Content editor' },
{ value: 'support', label: 'Support agent' },
];
const ASSIGNED = ['support'];
export function DensityScale() {
// items / value are forwarded as PROPERTIES by the wrapper — pass the arrays.
return (
<>
{[0, -1, -2, -3, -4].map((density) => (
<MdTransferList
key={density}
density={density}
sourceTitle={`Density ${density}`}
targetTitle="Assigned"
items={ROLES}
value={ASSIGNED}
/>
))}
</>
);
}import { Component } from '@angular/core';
@Component({
selector: 'app-density-scale',
template: `
<md-transfer-list
*ngFor="let density of densities"
[density]="density"
[sourceTitle]="'Density ' + density"
targetTitle="Assigned"
[items]="roles"
[value]="assigned"
></md-transfer-list>
`,
})
export class DensityScaleComponent {
densities = [0, -1, -2, -3, -4];
assigned = ['support'];
roles = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'content', label: 'Content editor' },
{ value: 'support', label: 'Support agent' },
];
}<script setup lang="ts">
import '@awc-ui/core/define';
const densities = [0, -1, -2, -3, -4];
const assigned = ['support'];
const roles = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'content', label: 'Content editor' },
{ value: 'support', label: 'Support agent' },
];
</script>
<template>
<md-transfer-list
v-for="density in densities"
:key="density"
:density="density"
:source-title="`Density ${density}`"
target-title="Assigned"
:items="roles"
:value="assigned"
/>
</template><script lang="ts">
import '@awc-ui/core/define';
const densities = [0, -1, -2, -3, -4];
const assigned = ['support'];
const roles = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'content', label: 'Content editor' },
{ value: 'support', label: 'Support agent' },
];
</script>
{#each densities as density}
<md-transfer-list
{density}
source-title="Density {density}"
target-title="Assigned"
items={roles}
value={assigned}
></md-transfer-list>
{/each}i18n — every string is a prop: source-title, target-title, both search
placeholders, empty-text, count-template, and the four move-*-labels.
count-template interpolates {checked} and {total} — keep both tokens.
Per the house i18n approach, resolve these from your dictionary in the consumer
layer rather than inside the component.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-transfer-list
id="tl-fr"
source-title="Rôles disponibles" target-title="Rôles attribués"
source-search-placeholder="Rechercher" target-search-placeholder="Rechercher"
count-template="{checked}/{total} sélectionnés"
empty-text="Aucun élément"
move-right-label="Déplacer la sélection vers la droite"
move-left-label="Déplacer la sélection vers la gauche"
move-all-right-label="Tout déplacer vers la droite"
move-all-left-label="Tout déplacer vers la gauche"
style="inline-size: 100%; --md-transfer-list-height: 260px;"
></md-transfer-list>
<script type="module">
var el = document.getElementById('tl-fr');
el.items = [
{ value: 'analytics', label: 'Analytique', description: 'Lire les tableaux de bord' },
{ value: 'billing', label: 'Facturation', description: 'Factures et paiements' },
{ value: 'content', label: 'Éditeur de contenu' },
{ value: 'support', label: 'Agent de support' },
];
el.value = [];
</script>import { MdTransferList } from '@awc-ui/react';
const items = [
{ value: 'analytics', label: 'Analytique', description: 'Lire les tableaux de bord' },
{ value: 'billing', label: 'Facturation', description: 'Factures et paiements' },
{ value: 'content', label: 'Éditeur de contenu' },
{ value: 'support', label: 'Agent de support' },
];
const value = [];
export function Demo() {
return (
<>
<MdTransferList
id="tl-fr"
items={items}
value={value}
sourceTitle="Rôles disponibles" targetTitle="Rôles attribués"
sourceSearchPlaceholder="Rechercher" targetSearchPlaceholder="Rechercher"
countTemplate="{checked}/{total} sélectionnés"
emptyText="Aucun élément"
moveRightLabel="Déplacer la sélection vers la droite"
moveLeftLabel="Déplacer la sélection vers la gauche"
moveAllRightLabel="Tout déplacer vers la droite"
moveAllLeftLabel="Tout déplacer vers la gauche"
style={{ inlineSize: '100%', '--md-transfer-list-height': '260px' }}
></MdTransferList>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts — the bound values live on the class
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent {
items = [
{ value: 'analytics', label: 'Analytique', description: 'Lire les tableaux de bord' },
{ value: 'billing', label: 'Facturation', description: 'Factures et paiements' },
{ value: 'content', label: 'Éditeur de contenu' },
{ value: 'support', label: 'Agent de support' },
];
value = [];
}
<!-- app.component.html -->
<md-transfer-list
id="tl-fr"
[items]="items"
[value]="value"
source-title="Rôles disponibles" target-title="Rôles attribués"
source-search-placeholder="Rechercher" target-search-placeholder="Rechercher"
count-template="{checked}/{total} sélectionnés"
empty-text="Aucun élément"
move-right-label="Déplacer la sélection vers la droite"
move-left-label="Déplacer la sélection vers la gauche"
move-all-right-label="Tout déplacer vers la droite"
move-all-left-label="Tout déplacer vers la gauche"
style="inline-size: 100%; --md-transfer-list-height: 260px;"
></md-transfer-list><script setup>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytique', description: 'Lire les tableaux de bord' },
{ value: 'billing', label: 'Facturation', description: 'Factures et paiements' },
{ value: 'content', label: 'Éditeur de contenu' },
{ value: 'support', label: 'Agent de support' },
];
const value = [];
</script>
<template>
<md-transfer-list
id="tl-fr"
:items="items"
:value="value"
source-title="Rôles disponibles" target-title="Rôles attribués"
source-search-placeholder="Rechercher" target-search-placeholder="Rechercher"
count-template="{checked}/{total} sélectionnés"
empty-text="Aucun élément"
move-right-label="Déplacer la sélection vers la droite"
move-left-label="Déplacer la sélection vers la gauche"
move-all-right-label="Tout déplacer vers la droite"
move-all-left-label="Tout déplacer vers la gauche"
style="inline-size: 100%; --md-transfer-list-height: 260px;"
></md-transfer-list>
</template><script>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytique', description: 'Lire les tableaux de bord' },
{ value: 'billing', label: 'Facturation', description: 'Factures et paiements' },
{ value: 'content', label: 'Éditeur de contenu' },
{ value: 'support', label: 'Agent de support' },
];
const value = [];
</script>
<md-transfer-list
id="tl-fr"
{items}
{value}
source-title="Rôles disponibles" target-title="Rôles attribués"
source-search-placeholder="Rechercher" target-search-placeholder="Rechercher"
count-template="{checked}/{total} sélectionnés"
empty-text="Aucun élément"
move-right-label="Déplacer la sélection vers la droite"
move-left-label="Déplacer la sélection vers la gauche"
move-all-right-label="Tout déplacer vers la droite"
move-all-left-label="Tout déplacer vers la gauche"
style="inline-size: 100%; --md-transfer-list-height: 260px;"
></md-transfer-list>| Custom property | Purpose |
|---|---|
--md-transfer-list-width / -min-width / -max-width | Overall inline-size bounds |
--md-transfer-list-height / -min-height / -max-height | Overall block-size bounds |
--md-transfer-list-column-min-width | Per-column floor before wrapping |
--md-transfer-list-column-max-block | Scroll height of each list |
--md-transfer-list-container-color / -container-shape | Column surface and radius |
--md-transfer-list-header-color / -header-text-color | Header tone |
--md-transfer-list-divider-color | Rule between header and list |
--md-transfer-list-item-padding / -item-gap / -item-inset | Row metrics |
--md-transfer-list-controls-gap / -control-size / -control-icon-size | Mover column metrics |
--md-transfer-list-empty-icon-size | Empty-state glyph size |
Every default resolves through an md-sys-color role, so a transfer list that
sets no custom properties follows the theme on its own:
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-transfer-list id="roles" source-title="Choices" target-title="Chosen"></md-transfer-list>
<script type="module">
var el = document.getElementById('tl-dark');
el.items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
el.value = ['support'];
</script>import { MdTransferList } from '@awc-ui/react';
const items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
const value = ['support'];
export function Demo() {
return (
<>
<MdTransferList id="roles" sourceTitle="Choices" targetTitle="Chosen"></MdTransferList>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts — the bound values live on the class
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent {
items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
value = ['support'];
}
<!-- app.component.html -->
<md-transfer-list id="roles" source-title="Choices" target-title="Chosen"></md-transfer-list><script setup>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
const value = ['support'];
</script>
<template>
<md-transfer-list id="roles" source-title="Choices" target-title="Chosen"></md-transfer-list>
</template><script>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytics' },
{ value: 'billing', label: 'Billing' },
{ value: 'support', label: 'Support agent' },
];
const value = ['support'];
</script>
<md-transfer-list id="roles" source-title="Choices" target-title="Chosen"></md-transfer-list>| Part | Element |
|---|---|
header / header-title / count | A pane’s header, its title, its counter |
search | A pane’s filter field |
list / item | The scroll region and each row |
actions | The move-button column between the panes |
<style>
md-transfer-list::part(header) { text-transform: uppercase; letter-spacing: .08em; }
md-transfer-list::part(header-title) { font-weight: 700; color: var(--md-sys-color-primary); }
md-transfer-list::part(count) { font-variant-numeric: tabular-nums; color: var(--md-sys-color-tertiary); }
md-transfer-list::part(list) { border-radius: 12px; outline: 1px solid var(--md-sys-color-outline-variant); }
md-transfer-list::part(actions) { gap: 12px; }
</style>
<md-transfer-list id="roles" source-title="Available" target-title="Assigned"></md-transfer-list>
<script type="module">
const el = document.getElementById('roles');
el.items = [/* … */];
el.value = ['support'];
</script>import { MdTransferList } from '@awc-ui/react';
const items = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'content', label: 'Content editor', description: 'Publish pages' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
const value = ['support'];
export function Demo() {
return (
<>
<style>
.tl-parts::part(header) { text-transform: uppercase; letter-spacing: .08em; }
.tl-parts::part(header-title) { font-weight: 700; color: var(--md-sys-color-primary); }
.tl-parts::part(count) { font-variant-numeric: tabular-nums; color: var(--md-sys-color-tertiary); }
.tl-parts::part(list) { border-radius: 12px; outline: 1px solid var(--md-sys-color-outline-variant); }
.tl-parts::part(actions) { gap: 12px; }
</style>
<MdTransferList
id="tl-parts"
items={items}
value={value}
className="tl-parts"
sourceTitle="Available"
targetTitle="Assigned"
style={{ inlineSize: '100%', '--md-transfer-list-height': '260px', '--md-transfer-list-column-min-width': '190px' }}
></MdTransferList>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts — the bound values live on the class
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent {
items = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'content', label: 'Content editor', description: 'Publish pages' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
value = ['support'];
}
<!-- app.component.html -->
<style>
.tl-parts::part(header) { text-transform: uppercase; letter-spacing: .08em; }
.tl-parts::part(header-title) { font-weight: 700; color: var(--md-sys-color-primary); }
.tl-parts::part(count) { font-variant-numeric: tabular-nums; color: var(--md-sys-color-tertiary); }
.tl-parts::part(list) { border-radius: 12px; outline: 1px solid var(--md-sys-color-outline-variant); }
.tl-parts::part(actions) { gap: 12px; }
</style>
<md-transfer-list
id="tl-parts"
[items]="items"
[value]="value"
class="tl-parts"
source-title="Available"
target-title="Assigned"
style="inline-size: 100%; --md-transfer-list-height: 260px; --md-transfer-list-column-min-width: 190px;"
></md-transfer-list><script setup>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'content', label: 'Content editor', description: 'Publish pages' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
const value = ['support'];
</script>
<template>
<style>
.tl-parts::part(header) { text-transform: uppercase; letter-spacing: .08em; }
.tl-parts::part(header-title) { font-weight: 700; color: var(--md-sys-color-primary); }
.tl-parts::part(count) { font-variant-numeric: tabular-nums; color: var(--md-sys-color-tertiary); }
.tl-parts::part(list) { border-radius: 12px; outline: 1px solid var(--md-sys-color-outline-variant); }
.tl-parts::part(actions) { gap: 12px; }
</style>
<md-transfer-list
id="tl-parts"
:items="items"
:value="value"
class="tl-parts"
source-title="Available"
target-title="Assigned"
style="inline-size: 100%; --md-transfer-list-height: 260px; --md-transfer-list-column-min-width: 190px;"
></md-transfer-list>
</template><script>
import '@awc-ui/core/define';
const items = [
{ value: 'analytics', label: 'Analytics', description: 'Read dashboards' },
{ value: 'billing', label: 'Billing', description: 'Invoices and payments' },
{ value: 'content', label: 'Content editor', description: 'Publish pages' },
{ value: 'support', label: 'Support agent', description: 'Answer tickets' },
{ value: 'devops', label: 'DevOps', description: 'Deploys and infra' },
];
const value = ['support'];
</script>
<style>
.tl-parts::part(header) { text-transform: uppercase; letter-spacing: .08em; }
.tl-parts::part(header-title) { font-weight: 700; color: var(--md-sys-color-primary); }
.tl-parts::part(count) { font-variant-numeric: tabular-nums; color: var(--md-sys-color-tertiary); }
.tl-parts::part(list) { border-radius: 12px; outline: 1px solid var(--md-sys-color-outline-variant); }
.tl-parts::part(actions) { gap: 12px; }
</style>
<md-transfer-list
id="tl-parts"
{items}
{value}
class="tl-parts"
source-title="Available"
target-title="Assigned"
style="inline-size: 100%; --md-transfer-list-height: 260px; --md-transfer-list-column-min-width: 190px;"
></md-transfer-list>md-multi-select ·
md-select ·
md-autocomplete ·
md-checkbox ·
md-list ·
md-text-field ·
md-icon-button
md-transfer-listTwo artefacts to give your AI agent so it generates correct UI with this component. The per-component spec answers "how do I use this exact tag?". The main-llm spec answers "which tag should I pick in the first place?".
md-transfer-list spec card
Identity · when to use / when NOT · decision cues · behavioural contract · do/don't · anti-patterns · full API. Paste into your agent when you're implementing with this component.
System-prompt preamble · decision matrix · token reference · page recipes · anti-patterns. Paste into the system prompt at the start of a piece of work.