Overview

Align is a widget that aligns its child within itself and optionally sizes itself based on the child’s size. It uses a normalized coordinate system to flexibly position the child.

Reference: https://api.flutter.dev/flutter/widgets/Align-class.html

When to Use

  • When you need to align a child widget at a specific position within its parent
  • When creating specific ratio margins around a child widget
  • When center-aligning content in dialogs or popups
  • When positioning buttons or icons at specific locations
  • When adjusting parent size proportionally to the child widget’s size

Basic Usage

// Center alignment (default)
Align({
  child: Text('Centered text')
})

// Top-right alignment
Align({
  alignment: Alignment.topRight,
  child: Icon(Icons.close)
})

// Using size factors
Align({
  widthFactor: 2.0,
  heightFactor: 1.5,
  child: Container({
    width: 100,
    height: 100,
    color: 'blue'
  })
})

Props

child (optional)

Type: Widget | undefined

The child widget to align.

alignment (optional)

Type: Alignment (default: Alignment.center)

Specifies the alignment position of the child widget. Alignment consists of x and y values, each ranging from -1.0 to 1.0.

Main Alignment constants:

  • Alignment.topLeft: (-1.0, -1.0)
  • Alignment.topCenter: (0.0, -1.0)
  • Alignment.topRight: (1.0, -1.0)
  • Alignment.centerLeft: (-1.0, 0.0)
  • Alignment.center: (0.0, 0.0)
  • Alignment.centerRight: (1.0, 0.0)
  • Alignment.bottomLeft: (-1.0, 1.0)
  • Alignment.bottomCenter: (0.0, 1.0)
  • Alignment.bottomRight: (1.0, 1.0)
// Custom alignment
Align({
  alignment: Alignment.of({ x: 0.5, y: -0.5 }), // Towards top-right
  child: Text('Custom alignment')
})

widthFactor (optional)

Type: number | undefined

Sets this widget’s width as a multiple of the child widget’s width. If null, expands to available width.

Align({
  widthFactor: 1.5, // 1.5 times child's width
  child: Container({
    width: 100,
    height: 50,
    color: 'red'
  })
})
// Align's width becomes 150

heightFactor (optional)

Type: number | undefined

Sets this widget’s height as a multiple of the child widget’s height. If null, expands to available height.

Align({
  heightFactor: 2.0, // 2 times child's height
  child: Container({
    width: 100,
    height: 50,
    color: 'green'
  })
})
// Align's height becomes 100

Layout Behavior

Size Constraint Handling

  1. Under bounded constraints:

    • If widthFactor and heightFactor are null, expands to available size
    • Layouts child with loosened constraints
    • Positions child according to alignment
  2. Under unbounded constraints:

    • If widthFactor and heightFactor are null, shrinks to child size
    • If factors are specified, size is determined by child size × factor
  3. When using size factors:

    • Always sizes to child size × factor
    • Works independently of parent constraints

Practical Examples

Example 1: Close Button Alignment

const DialogWithCloseButton = ({ title, content, onClose }) => {
  return Container({
    padding: EdgeInsets.all(20),
    decoration: BoxDecoration({
      color: 'white',
      borderRadius: BorderRadius.circular(12),
      boxShadow: [
        BoxShadow({
          color: 'rgba(0, 0, 0, 0.1)',
          blurRadius: 10,
          offset: { x: 0, y: 4 }
        })
      ]
    }),
    child: Stack({
      children: [
        Column({
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, {
              style: TextStyle({
                fontSize: 20,
                fontWeight: 'bold'
              })
            }),
            SizedBox({ height: 12 }),
            Text(content)
          ]
        }),
        Align({
          alignment: Alignment.topRight,
          child: GestureDetector({
            onTap: onClose,
            child: Icon(Icons.close, { size: 24 })
          })
        })
      ]
    })
  });
};

Example 2: Centered Loading Indicator

const LoadingOverlay = ({ isLoading, child }) => {
  return Stack({
    children: [
      child,
      if (isLoading) Container({
        color: 'rgba(0, 0, 0, 0.5)',
        child: Align({
          alignment: Alignment.center,
          child: Container({
            padding: EdgeInsets.all(20),
            decoration: BoxDecoration({
              color: 'white',
              borderRadius: BorderRadius.circular(8)
            }),
            child: Column({
              mainAxisSize: MainAxisSize.min,
              children: [
                CircularProgressIndicator(),
                SizedBox({ height: 16 }),
                Text('Loading...')
              ]
            })
          })
        })
      })
    ]
  });
};

Example 3: Badge Positioning

const IconWithBadge = ({ icon, badgeCount }) => {
  return Container({
    width: 48,
    height: 48,
    child: Stack({
      children: [
        Center({
          child: Icon(icon, { size: 32 })
        }),
        if (badgeCount > 0) Align({
          alignment: Alignment.topRight,
          child: Container({
            padding: EdgeInsets.all(4),
            decoration: BoxDecoration({
              color: 'red',
              shape: BoxShape.circle
            }),
            constraints: BoxConstraints({
              minWidth: 20,
              minHeight: 20
            }),
            child: Center({
              child: Text(badgeCount.toString(), {
                style: TextStyle({
                  color: 'white',
                  fontSize: 12,
                  fontWeight: 'bold'
                })
              })
            })
          })
        })
      ]
    })
  });
};

Example 4: Tooltip Alignment

class TooltipWidget extends StatefulWidget {
  message: string;
  child: Widget;
  alignment: Alignment;

  constructor({ message, child, alignment = Alignment.topCenter }) {
    super();
    this.message = message;
    this.child = child;
    this.alignment = alignment;
  }

  createState(): State<TooltipWidget> {
    return new TooltipWidgetState();
  }
}

class TooltipWidgetState extends State<TooltipWidget> {
  isVisible = false;

  build(): Widget {
    return Stack({
      clipBehavior: Clip.none,
      children: [
        GestureDetector({
          onMouseEnter: () => {
            this.setState(() => {
              this.isVisible = true;
            });
          },
          onMouseLeave: () => {
            this.setState(() => {
              this.isVisible = false;
            });
          },
          child: this.widget.child
        }),
        if (this.isVisible) Align({
          alignment: this.widget.alignment,
          widthFactor: 1.0,
          child: Transform.translate({
            offset: { x: 0, y: this.widget.alignment.y < 0 ? -30 : 30 },
            child: Container({
              padding: EdgeInsets.symmetric({ horizontal: 12, vertical: 6 }),
              decoration: BoxDecoration({
                color: 'rgba(0, 0, 0, 0.8)',
                borderRadius: BorderRadius.circular(4)
              }),
              child: Text(this.widget.message, {
                style: TextStyle({
                  color: 'white',
                  fontSize: 14
                })
              })
            })
          })
        })
      ]
    });
  }
}

Example 5: Responsive Card Layout

const ResponsiveCard = ({ title, subtitle, action }) => {
  return Container({
    padding: EdgeInsets.all(16),
    decoration: BoxDecoration({
      border: Border.all({ color: '#E0E0E0' }),
      borderRadius: BorderRadius.circular(8)
    }),
    child: Row({
      children: [
        Expanded({
          child: Column({
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(title, {
                style: TextStyle({
                  fontSize: 18,
                  fontWeight: 'bold'
                })
              }),
              SizedBox({ height: 4 }),
              Text(subtitle, {
                style: TextStyle({
                  color: '#666',
                  fontSize: 14
                })
              })
            ]
          })
        }),
        Align({
          alignment: Alignment.centerRight,
          widthFactor: 1.2,
          child: action
        })
      ]
    })
  });
};

Important Notes

  • widthFactor and heightFactor must be greater than 0
  • alignment x and y values must be between -1.0 and 1.0
  • When there’s no child and widthFactor or heightFactor is null, size may become 0
  • Understand the difference between Align and Positioned when using within Stack
  • Using size factors can impact layout performance
  • Loosens constraints to allow the child to have its desired size
  • Center: Special case of Align, always centers content
  • Positioned: Absolute positioning within Stack
  • Container: Can align children using alignment property
  • FractionallySizedBox: Sizes based on parent’s dimensions ratio
  • AnimatedAlign: Animated transitions between alignments