A client-side library to make absolutely positioned elements attach to elements in the page efficiently.
—
Advanced positioning constraints to keep elements within specified boundaries and handle viewport clipping. The constraint system prevents elements from being positioned outside visible areas and provides intelligent attachment point flipping.
Define boundaries and behaviors for positioned elements.
interface ConstraintOptions {
/** Boundary definition - can be a selector, element, or coordinate array */
to: string | HTMLElement | Array<number>;
/** How to handle constraint violations - flip attachments */
attachment?: string;
/** Whether and how to pin elements to boundaries */
pin?: boolean | string | Array<string>;
/** Custom CSS class for out-of-bounds state */
outOfBoundsClass?: string;
/** Custom CSS class for pinned state */
pinnedClass?: string;
}Usage in Tether Options:
import Tether from "tether";
const tether = new Tether({
element: '.dropdown',
target: '.trigger',
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'scrollParent',
attachment: 'together',
pin: true
},
{
to: 'window',
attachment: 'together',
pin: ['top', 'left']
}
]
});Different types of boundaries for constraint checking.
// String boundary identifiers
type BoundaryIdentifier =
| "scrollParent" // First scrollable ancestor
| "window" // Browser viewport
| string; // CSS selector
// Element boundary
type ElementBoundary = HTMLElement;
// Coordinate boundary [left, top, right, bottom]
type CoordinateBoundary = [number, number, number, number];Usage Examples:
// Constrain to viewport
{
to: 'window',
attachment: 'together',
pin: true
}
// Constrain to scrollable parent
{
to: 'scrollParent',
attachment: 'both',
pin: ['top', 'bottom']
}
// Constrain to specific element
{
to: '.container',
attachment: 'target',
pin: true
}
// Constrain to custom coordinates
{
to: [100, 50, 800, 600], // [left, top, right, bottom]
attachment: 'together',
pin: true
}Control how attachment points change when constraints are violated.
type AttachmentFlipBehavior =
| "target" // Flip target attachment only
| "element" // Flip element attachment only
| "both" // Flip both attachments independently
| "together"; // Flip both attachments as a unitAttachment Behaviors:
// Flip target attachment when constrained
{
to: 'window',
attachment: 'target',
pin: false
}
// Flip both attachments together
{
to: 'scrollParent',
attachment: 'together',
pin: true
}Control how elements are pinned to constraint boundaries.
type PinConfiguration =
| boolean // Pin to all sides
| string // Single side or comma-separated sides
| Array<string>; // Array of sides
type PinSide = "top" | "bottom" | "left" | "right";Usage Examples:
// Pin to all sides
{ pin: true }
// Pin to specific sides
{ pin: 'top,left' }
{ pin: ['top', 'left'] }
// No pinning (allow out-of-bounds)
{ pin: false }Override default CSS classes for constraint states.
interface CustomConstraintClasses {
/** Class applied when element is outside boundaries */
outOfBoundsClass?: string;
/** Class applied when element is pinned to boundaries */
pinnedClass?: string;
}Usage Example:
const tether = new Tether({
element: '.menu',
target: '.button',
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'window',
attachment: 'together',
pin: true,
outOfBoundsClass: 'menu-overflow',
pinnedClass: 'menu-pinned'
}
]
});// Dropdown that flips when near viewport edges
const dropdown = new Tether({
element: '.dropdown-menu',
target: '.dropdown-trigger',
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
// Flip attachments together when hitting viewport
to: 'window',
attachment: 'together',
pin: false
},
{
// Pin to scrollable container
to: 'scrollParent',
pin: ['top', 'bottom', 'left', 'right']
}
]
});// Tooltip that stays within viewport
const tooltip = new Tether({
element: '.tooltip',
target: '.help-icon',
attachment: 'bottom center',
targetAttachment: 'top center',
constraints: [
{
to: 'window',
attachment: 'together',
pin: ['top', 'left', 'right']
}
]
});// Modal that stays centered in viewport
const modal = new Tether({
element: '.modal',
target: 'body',
attachment: 'middle center',
targetAttachment: 'middle center',
constraints: [
{
to: 'window',
pin: ['top', 'left', 'right', 'bottom']
}
]
});The constraint system automatically applies CSS classes based on positioning state:
// Out of bounds classes
".tether-out-of-bounds" // General out-of-bounds state
".tether-out-of-bounds-top" // Out of bounds on top
".tether-out-of-bounds-bottom" // Out of bounds on bottom
".tether-out-of-bounds-left" // Out of bounds on left
".tether-out-of-bounds-right" // Out of bounds on right
// Pinned classes
".tether-pinned" // General pinned state
".tether-pinned-top" // Pinned to top
".tether-pinned-bottom" // Pinned to bottom
".tether-pinned-left" // Pinned to left
".tether-pinned-right" // Pinned to rightWhen outOfBoundsClass or pinnedClass are specified, they replace the default classes:
// With custom classes
{
outOfBoundsClass: 'menu-overflow',
pinnedClass: 'menu-stuck'
}
// Applied classes become:
".menu-overflow" // Instead of .tether-out-of-bounds
".menu-overflow-top" // Instead of .tether-out-of-bounds-top
".menu-stuck" // Instead of .tether-pinned
".menu-stuck-left" // Instead of .tether-pinned-leftThe Tether system also applies these additional classes:
// Marker classes (for debugging/development)
".tether-element-marker" // Marker on positioned element
".tether-target-marker" // Marker on target element
".tether-marker-dot" // Visual dot within markersInstall with Tessl CLI
npx tessl i tessl/npm-tether