Button | Dashforge-UI
DocsStarter Kits
v0.1.0-alpha

Button

An action component with built-in RBAC support for declarative access control. Wraps MUI Button with permission-based rendering and disabling. Perfect for primary actions, destructive operations, publishing workflows, and contextual toolbar actions.

Quick Start

Copy & Paste

import { Button } from '@dashforge/ui';

<Button onClick={handleClick}>Click Me</Button>

Examples

Common Button patterns and variants

Basic Button

<Button onClick={handleClick}>
  Click Me
</Button>

Primary Action

<Button variant="contained" onClick={handleSubmit}>
  Submit Form
</Button>

Disabled State

<Button variant="contained" disabled>
  Cannot Click
</Button>

With RBAC

<Button
  variant="contained"
  color="error"
  access={{
    resource: 'user',
    action: 'delete',
    onUnauthorized: 'hide'
  }}
  onClick={handleDelete}
>
  Delete User
</Button>

Button Variants

MUI Button variants for different UI contexts

Contained (Primary)

<Button variant="contained" onClick={handleSave}>
  Save Changes
</Button>

Outlined (Secondary)

<Button variant="outlined" onClick={handleCancel}>
  Cancel
</Button>

Text (Tertiary)

<Button variant="text" onClick={handleReset}>
  Reset
</Button>

Destructive Actions

<Button variant="contained" color="error" onClick={handleDelete}>
  Delete User
</Button>

Dashforge Capabilities

What makes Button powerful in the Dashforge ecosystem

Full MUI Button API

All MUI Button props are supported: variant, color, size, startIcon, endIcon, fullWidth, and more. Button is a thin wrapper that preserves the complete MUI ergonomics.

Built-in RBAC Support

Declarative access control via the access prop. No need for scattered useCan() checks in userland. Hide, disable, or fallback to disabled based on user permissions.

Action Authorization

Perfect for controlling primary actions (Save, Submit), destructive operations (Delete, Archive), publishing workflows (Publish, Approve), and contextual actions (Edit, Duplicate).

Explicit Readonly Fallback

Buttons are action components without readonly semantics. When onUnauthorized: "readonly" is used, the button explicitly falls back to disabled state for safe, predictable behavior.

OR Logic for Disabled State

Combines explicit disabled prop with RBAC access state. Button is disabled if either source requires it, allowing you to mix authorization with loading states and validation.

Zero Dependencies on Form System

Button does not depend on DashFormContext, react-hook-form, or @dashforge/forms. It only depends on @dashforge/rbac for access control, making it universally usable.


Access Control (RBAC)

Control button visibility and interaction based on user permissions. Buttons can be hidden or disabled when users lack the required access. Integrates seamlessly with the Dashforge RBAC system for declarative action authorization.

Hide when unauthorized

<Button
  variant="contained"
  access={{
    resource: 'user',
    action: 'delete',
    onUnauthorized: 'hide',
  }}
>
  Delete User
</Button>

Disable when cannot execute

<Button
  variant="contained"
  access={{
    resource: 'article',
    action: 'publish',
    onUnauthorized: 'disable',
  }}
>
  Publish Article
</Button>

Readonly fallback

<Button
  variant="contained"
  access={{
    resource: 'invoice',
    action: 'approve',
    onUnauthorized: 'readonly',
  }}
>
  Approve Invoice
</Button>

Combined with disabled state

<Button
  variant="contained"
  disabled={isPublishing}
  access={{
    resource: 'article',
    action: 'publish',
    onUnauthorized: 'disable',
  }}
>
  Publish
</Button>

Note: Buttons do not support readonly semantics. When onUnauthorized: 'readonly' is used, it falls back to disabled for safety. Combine access with explicit disabled for loading states or business rules.


Action Integration Scenarios

Real-world usage in forms, toolbars, and workflows

Form Submit Actions

function ArticleForm() {
  const { handleSubmit, formState } = useForm();

  const onSubmit = async (data) => {
    await saveArticle(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Form fields... */}
      
      <Stack direction="row" spacing={2}>
        <Button
          variant="contained"
          type="submit"
          disabled={formState.isSubmitting}
        >
          {formState.isSubmitting ? 'Saving...' : 'Save Draft'}
        </Button>
        
        <Button
          variant="contained"
          color="primary"
          disabled={formState.isSubmitting}
          access={{
            resource: 'article',
            action: 'publish',
            onUnauthorized: 'disable'
          }}
          onClick={handlePublish}
        >
          Publish
        </Button>
      </Stack>
    </form>
  );
}

Destructive Actions with Confirmation

function UserActions({ userId }) {
  const confirm = useConfirm();

  const handleDelete = async () => {
    const result = await confirm({
      title: 'Delete User',
      message: 'This action cannot be undone.',
      confirmText: 'Delete',
      cancelText: 'Cancel'
    });
    
    if (result.confirmed) {
      await deleteUser(userId);
    }
  };

  return (
    <Button
      variant="contained"
      color="error"
      access={{
        resource: 'user',
        action: 'delete',
        onUnauthorized: 'hide'
      }}
      onClick={handleDelete}
    >
      Delete User
    </Button>
  );
}

Toolbar Actions

function ArticleToolbar({ article }) {
  return (
    <Stack direction="row" spacing={1}>
      <Button
        variant="outlined"
        size="small"
        access={{
          resource: 'article',
          action: 'edit',
          onUnauthorized: 'hide'
        }}
        onClick={handleEdit}
      >
        Edit
      </Button>
      
      <Button
        variant="outlined"
        size="small"
        access={{
          resource: 'article',
          action: 'duplicate',
          onUnauthorized: 'hide'
        }}
        onClick={handleDuplicate}
      >
        Duplicate
      </Button>
      
      <Button
        variant="outlined"
        size="small"
        color="error"
        access={{
          resource: 'article',
          action: 'archive',
          onUnauthorized: 'hide'
        }}
        onClick={handleArchive}
      >
        Archive
      </Button>
    </Stack>
  );
}

API Reference

Complete props documentation

RBAC and Disabled State: Button combines explicit disabled prop with RBAC access state using OR logic. The button is disabled if either the explicit prop is true OR RBAC requires it (onUnauthorized: 'disable' or 'readonly').

PropTypeDefaultDescription
accessAccessRequirement-RBAC access control requirement. Controls button visibility and interaction based on user permissions. Supports hide, disable, and readonly (falls back to disabled) strategies.
disabledbooleanfalseIf true, the button is disabled. Combines with RBAC access state via OR logic.
variant'text' | 'outlined' | 'contained''text'Button visual style
color'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning''primary'Button color theme
size'small' | 'medium' | 'large''medium'Button size
onClick(event: React.MouseEvent<HTMLButtonElement>) => void-Callback fired when the button is clicked
childrenReact.ReactNode-Button content (text, icons, etc.)
startIconReact.ReactNode-Element placed before the children
endIconReact.ReactNode-Element placed after the children
fullWidthbooleanfalseIf true, the button takes up the full width of its container
type'button' | 'submit' | 'reset''button'HTML button type attribute
hrefstring-URL to link to when the button is clicked. Renders as anchor element when provided.

Type Definitions

interface ButtonProps extends Omit<MuiButtonProps, 'disabled'> {
  access?: AccessRequirement;
  disabled?: boolean;
}

interface AccessRequirement {
  resource: string;
  action: string;
  onUnauthorized: 'hide' | 'disable' | 'readonly';
}

Note: All other MUI Button props are supported through prop forwarding (component, sx, disableRipple, etc.). See MUI Button API for complete prop reference.


Implementation Notes

Important details and best practices

Readonly Fallback Behavior

Buttons are action components without readonly semantics. When access.onUnauthorized is set to "readonly", the button falls back to disabled state. This is an explicit, safe choice: a disabled button clearly signals that the action cannot be performed, preventing unintended execution.

OR Logic for Disabled State

The effective disabled state uses OR logic: effectiveDisabled = disabled || accessState.disabled || accessState.readonly. This means the button is disabled if ANY source requires it (explicit prop, RBAC disable, or RBAC readonly fallback).

RBAC is Authorization, Not All UI Logic

RBAC controls permissions (can user perform this action?), not application state (is action currently in progress?). Combine the access prop with explicit disabled for loading states, validation failures, or business rules.

Not a Form Field

Button does not integrate with DashFormContext or react-hook-form. It is a pure action component. Use it for triggering actions, not for form field behavior. For form submission, use type="submit" with standard HTML form semantics.

Hide vs Disable

Use onUnauthorized: "hide" when the action should not be visible to unauthorized users (e.g., Delete User). Use onUnauthorized: "disable" when the action should be visible but not executable (e.g., Publish Article when user can view but not publish).

Full MUI Button API

All MUI Button props are supported and forwarded correctly: variant, color, size, startIcon, endIcon, fullWidth, href, component, type, and more. Button is a thin wrapper that preserves complete MUI ergonomics.


On This Page