Usage
Button triggers an action or navigates to a destination. Import Button from @udixio/ui-react, @udixio/ui-angular or @udixio/ui-svelte. Provide visible text with React children, Angular projected content, a Svelte children snippet, or the shared label property. React accepts exactly one of label and children, which keeps the visible and accessible labels from contradicting each other.
Action buttons render a native <button> and default to type="button". Providing href renders a native <a> instead.
Variants
The canonical variants are filled, elevated, tonal, outlined, and text. The compatibility aliases primary and secondary resolve to filled and tonal.
import { Button } from '@udixio/ui-react';
export default function ButtonVariantsReact() {
return (
<div className="flex flex-wrap justify-center gap-3">
<Button label="Filled" variant="filled" />
<Button label="Elevated" variant="elevated" />
<Button label="Tonal" variant="tonal" />
<Button label="Outlined" variant="outlined" />
<Button label="Text" variant="text" />
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Button } from '@udixio/ui-angular';
@Component({
selector: 'docs-button-variants-angular',
standalone: true,
imports: [Button],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-wrap justify-center gap-3">
<udx-button label="Filled" variant="filled" />
<udx-button label="Elevated" variant="elevated" />
<udx-button label="Tonal" variant="tonal" />
<udx-button label="Outlined" variant="outlined" />
<udx-button label="Text" variant="text" />
</div>
`,
})
export class ButtonVariantsAngular {}<script lang="ts">
import { Button } from '@udixio/ui-svelte';
</script>
<div class="flex flex-wrap justify-center gap-3">
<Button label="Filled" variant="filled" />
<Button label="Elevated" variant="elevated" />
<Button label="Tonal" variant="tonal" />
<Button label="Outlined" variant="outlined" />
<Button label="Text" variant="text" />
</div>Toggle buttons
toggleable enables toggle-button semantics. pressed is the controlled value; defaultPressed is captured once for uncontrolled usage. React emits onPressedChange, Angular emits pressedChange, and Svelte exposes bind:pressed next to onPressedChange — reject a change with a function binding whose setter decides. onClick, (click) and onclick remain independent action events.
import { useState } from 'react';
import { Button } from '@udixio/ui-react';
export default function ButtonToggleReact() {
const [pressed, setPressed] = useState(false);
return (
<Button
label="Notifications"
toggleable
pressed={pressed}
onPressedChange={setPressed}
variant="tonal"
/>
);
}import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { Button } from '@udixio/ui-angular';
@Component({
selector: 'docs-button-toggle-angular',
standalone: true,
imports: [Button],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<udx-button
label="Notifications"
toggleable
[pressed]="pressed()"
(pressedChange)="pressed.set($event)"
variant="tonal"
/>
`,
})
export class ButtonToggleAngular {
protected readonly pressed = signal(false);
}<script lang="ts">
import { Button } from '@udixio/ui-svelte';
let pressed = $state(false);
</script>
<Button label="Notifications" toggleable bind:pressed variant="tonal" />The visible label stays stable while aria-pressed communicates the state. Navigation links ignore toggle state; mark the current destination with aria-current instead.
Sizes
Sizes range from xSmall to xLarge. Every size includes a 48px pointer target, including compact visual treatments.
import { Button } from '@udixio/ui-react';
export default function ButtonSizesReact() {
return (
<div className="flex flex-wrap items-end justify-center gap-3">
<Button label="XS" size="xSmall" />
<Button label="S" size="small" />
<Button label="M" size="medium" />
<Button label="L" size="large" />
<Button label="XL" size="xLarge" />
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Button } from '@udixio/ui-angular';
@Component({
selector: 'docs-button-sizes-angular',
standalone: true,
imports: [Button],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-wrap items-end justify-center gap-3">
<udx-button label="XS" size="xSmall" />
<udx-button label="S" size="small" />
<udx-button label="M" size="medium" />
<udx-button label="L" size="large" />
<udx-button label="XL" size="xLarge" />
</div>
`,
})
export class ButtonSizesAngular {}<script lang="ts">
import { Button } from '@udixio/ui-svelte';
</script>
<div class="flex flex-wrap items-end justify-center gap-3">
<Button label="XS" size="xSmall" />
<Button label="S" size="small" />
<Button label="M" size="medium" />
<Button label="L" size="large" />
<Button label="XL" size="xLarge" />
</div>States and shapes
disabled and loading block actions and pressed-state changes. Loading also sets aria-busy while keeping the label available as the accessible name. shape selects the rounded or squared resting shape.
import { Button } from '@udixio/ui-react';
export default function ButtonStatesReact() {
return (
<div className="flex flex-wrap items-center justify-center gap-3">
<Button label="Disabled" disabled />
<Button label="Sending" loading />
<Button label="Rounded" shape="rounded" variant="tonal" />
<Button label="Squared" shape="squared" variant="outlined" />
<Button label="Static shape" shapeFeedback="none" variant="text" />
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Button } from '@udixio/ui-angular';
@Component({
selector: 'docs-button-states-angular',
standalone: true,
imports: [Button],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-wrap items-center justify-center gap-3">
<udx-button label="Disabled" disabled />
<udx-button label="Sending" loading />
<udx-button label="Rounded" shape="rounded" variant="tonal" />
<udx-button label="Squared" shape="squared" variant="outlined" />
<udx-button label="Static shape" shapeFeedback="none" variant="text" />
</div>
`,
})
export class ButtonStatesAngular {}<script lang="ts">
import { Button } from '@udixio/ui-svelte';
</script>
<div class="flex flex-wrap items-center justify-center gap-3">
<Button label="Disabled" disabled />
<Button label="Sending" loading />
<Button label="Rounded" shape="rounded" variant="tonal" />
<Button label="Squared" shape="squared" variant="outlined" />
<Button label="Static shape" shapeFeedback="none" variant="text" />
</div>By default, the shared Motion controller transforms the button shape on press. Set shapeFeedback="none" in any framework to keep a static shape, or pass a Motion transition to customize the morph feedback. Reduced-motion preferences keep the feedback but remove the movement.
Text buttons keep their full layout box by default. Set edgeAligned / [edgeAligned]="true" when the button should align to the surrounding inline edge with negative inline margins.
Icons
iconPosition uses the logical values start and end, so placement follows the page direction in RTL layouts. The historical left and right aliases remain supported. Button icons are decorative; the text label names the action.
import { iAdd } from '@udixio/icons-rounded-400/add';
import { Button } from '@udixio/ui-react';
export default function ButtonIconsReact() {
return (
<div className="flex flex-wrap justify-center gap-3">
<Button label="Add" icon={iAdd} />
<Button label="Next" icon={iAdd} iconPosition="end" />
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { iAdd } from '@udixio/icons-rounded-400/add';
import { Button } from '@udixio/ui-angular';
@Component({
selector: 'docs-button-icons-angular',
standalone: true,
imports: [Button],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-wrap justify-center gap-3">
<udx-button label="Add" [icon]="addIcon" />
<udx-button label="Next" [icon]="addIcon" iconPosition="end" />
</div>
`,
})
export class ButtonIconsAngular {
protected readonly addIcon = iAdd;
}<script lang="ts">
import { Button } from '@udixio/ui-svelte';
import { iAdd } from '@udixio/icons-rounded-400/add';
</script>
<div class="flex flex-wrap justify-center gap-3">
<Button label="Add" icon={iAdd} />
<Button label="Next" icon={iAdd} iconPosition="end" />
</div>Link or action
React forwards native button or anchor attributes. Angular exposes the common link attributes target, rel, tabIndex, aria-label, aria-describedby, and aria-current on the inner interactive element. Svelte spreads the attributes a button and a link share onto the root element, plus target and rel. A disabled or loading link has no href, receives aria-disabled="true", and leaves the tab order.
No action yet
import { useState } from 'react';
import { Button } from '@udixio/ui-react';
export default function ButtonActionsReact() {
const [message, setMessage] = useState('No action yet');
return (
<div className="flex flex-col items-center gap-3">
<div className="flex flex-wrap justify-center gap-3">
<Button label="Run action" onClick={() => setMessage('Action run')} />
<Button
label="Button documentation"
href="/components/button/overview"
aria-current="page"
variant="outlined"
/>
</div>
<p aria-live="polite" className="text-body-medium">
{message}
</p>
</div>
);
}No action yet
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { Button } from '@udixio/ui-angular';
@Component({
selector: 'docs-button-actions-angular',
standalone: true,
imports: [Button],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-3">
<div class="flex flex-wrap justify-center gap-3">
<udx-button label="Run action" (click)="message.set('Action run')" />
<udx-button
label="Button documentation"
href="/components/button/overview"
aria-current="page"
variant="outlined"
/>
</div>
<p aria-live="polite" class="text-body-medium">{{ message() }}</p>
</div>
`,
})
export class ButtonActionsAngular {
protected readonly message = signal('No action yet');
}<script lang="ts">
import { Button } from '@udixio/ui-svelte';
let message = $state('');
</script>
<div class="flex flex-col items-center gap-3">
<div class="flex flex-wrap justify-center gap-3">
<Button label="Run action" onclick={() => (message = 'Action run')} />
<Button
label="Button documentation"
href="/components/button/overview"
aria-current="page"
variant="outlined"
/>
</div>
<p aria-live="polite" class="text-body-medium">{message}</p>
</div>Accessibility
Button relies on native button and link keyboard behavior. Toggle buttons expose aria-pressed; loading buttons expose aria-busy; and keyboard focus receives a visible two-pixel outline. Always provide a concise accessible name. When custom visual content has no accessible text, provide aria-label in every framework. Never provide a label that contradicts visible text.
Migration to the next major
Button’s stable contract intentionally has no compatibility aliases. Update consumers before adopting the next major release:
| Previous API | Stable API |
|---|---|
disableTextMargins | edgeAligned with inverted polarity |
allowShapeTransformation={false} | shapeFeedback="none" |
activated | pressed or defaultPressed |
onToggle | toggleable plus onPressedChange / pressedChange |
React label together with children | Choose one; add aria-label for non-text children |
edgeAligned defaults to false and shapeFeedback to morph. Toggle state now follows the controlled and uncontrolled contract described above, and action events remain independent.