Why TagHeadPosition Won't Give You a Corner
The Revit API does not expose a corner-based position for tag heads natively. TagHeadPosition is always the insertion point of the tag family, which in virtually all standard Revit tag families is defined at the center of the label. So there's no alternative property you're missing — it simply doesn't exist at the API level for corners.
The Bounding Box Problem — Leader Lines
Yes, bounding boxes in Revit do include leader lines in their extents. This is exactly why your bounding box approach is giving you random-feeling results — tags with leaders pointing in different directions will have wildly different Min and Max values even if the tag head itself is the same size.
Hiding leader lines in the view does not remove them from the bounding box calculation. The geometry is still there; it's just not displayed.
The Right Approach: Derive the Corner from TagHeadPosition + Half-Extents
The cleanest way to avoid MoveElement while still getting corner alignment is to compute the tag head's half-width and half-height independently of the leader, and then offset TagHeadPosition by that amount.
Here's the logic:
// Get the bounding box in the active view
BoundingBoxXYZ bbox = tag.get_BoundingBox(doc.ActiveView);
XYZ center = tag.TagHeadPosition;
// Compute the bbox center (which may differ from TagHeadPosition due to leader)
XYZ bboxCenter = new XYZ(
(bbox.Min.X + bbox.Max.X) / 2.0,
(bbox.Min.Y + bbox.Max.Y) / 2.0,
center.Z);
// The offset between TagHeadPosition and bbox center
// is caused by the leader. We subtract it to isolate tag head extents.
// Instead, derive half-size from the family itself (see below).But since the bounding box is polluted by the leader, the better move is to measure the tag head size from a tag that has no leader, or use a known reference tag to calibrate.
Recommended Pattern: Calibrate from a Leader-less Tag
// Temporarily suppress the leader or use a tag you know has no leader
// to get a clean bounding box representing just the tag head
BoundingBoxXYZ cleanBox = referenceTag.get_BoundingBox(doc.ActiveView);
double halfWidth = (cleanBox.Max.X - cleanBox.Min.X) / 2.0;
double halfHeight = (cleanBox.Max.Y - cleanBox.Min.Y) / 2.0;
// Now you can compute the left edge of any tag as:
XYZ headCenter = anyTag.TagHeadPosition;
double leftEdge = headCenter.X - halfWidth;
double bottomEdge = headCenter.Y - halfHeight;
Once you have halfWidth and halfHeight as constants (they're the same for all tags of the same family and type), you can do your alignment purely through TagHeadPosition without touching MoveElement or bounding boxes at all.
Fixing Your TagHeadPosition Code
Your second block was on the right track. Here's a corrected version that left-aligns tags using TagHeadPosition with the half-width offset:
transaction.Start();
// Compute half-width once from a clean reference tag (no leader, or known size)
BoundingBoxXYZ refBox = bottomTag.get_BoundingBox(doc.ActiveView);
double halfWidth = (refBox.Max.X - refBox.Min.X) / 2.0;
double halfHeight = (refBox.Max.Y - refBox.Min.Y) / 2.0;
// Get the LEFT edge of the bottom tag
XYZ bottomCenter = bottomTag.TagHeadPosition;
double leftEdge = bottomCenter.X - halfWidth;
// Position topTag so its left edge aligns and it sits directly above
XYZ topCenter = topTag.TagHeadPosition;
// New center X: leftEdge + halfWidth (to keep same tag width)
// New center Y: bottom of bottomTag + height of topTag's half
double newX = leftEdge + halfWidth;
double newY = bottomCenter.Y + halfHeight + halfHeight; // stack them with no gap
topTag.TagHeadPosition = new XYZ(newX, newY, topCenter.Z);
transaction.Commit();
If you want a gap between tags, just add a padding value to newY:
double padding = 0.1; // in feet, adjust as needed
double newY = bottomCenter.Y + halfHeight + halfHeight + padding;
Summary of Key Points
Issue Reality
| TagHeadPosition corner access | Not available — always center |
| Leader lines in bounding box | Yes, included — this is your main bug |
| Hiding leaders removes them from bbox | No, they stay in the calculation |
| Best workaround | Calibrate half-width/height from a leader-less reference tag, then use TagHeadPosition exclusively |