A powerful and flexible resizer widget for TiddlyWiki that enables interactive resizing of UI elements with support for multiple tiddlers, various CSS units, and calc() expressions.
- Multi-tiddler Support: Resize multiple tiddlers simultaneously using filter expressions
- Comprehensive Unit Support: Works with all CSS units (px, %, em, rem, vh, vw, vmin, vmax)
- CSS calc() Expressions: Use complex calculations for min/max values like calc(100% - 350px)
- Unit Preservation: Maintains each tiddler's original unit type while ensuring consistent resize behavior
- Smart Unit Conversion: Automatically converts between units when needed
- Constraint System: Enforces min/max limits across all target tiddlers as a group
- Live Preview: Optional real-time visual feedback during resizing
- Directional Control: Supports both horizontal and vertical resizing
- Aspect Ratio: Maintain aspect ratios during resize operations
- Touch Support: Works with both mouse and touch input via pointer events
- Double-Click Reset: Double-click any resizer handle to reset to default/min/max values
- Enhanced Handle Styles: Choose from multiple handle visual styles (solid, dots, lines, chevron, grip)
- Haptic Feedback: Tactile feedback on mobile devices for better user experience
- Go to burningtreec.github.io/resizer/
- Drag and drop the plugin file into your TiddlyWiki
- Save and reload your wiki
<$resizer
  direction="horizontal"
  tiddler="$:/themes/tiddlywiki/vanilla/metrics/sidebarwidth"
  min="200px"
  max="800px"
  default="350px"
/>| Attribute | Description | Default | 
|---|---|---|
| direction | Resize direction: "horizontal" or "vertical" | "horizontal" | 
| tiddler | Target tiddler | - | 
| filter | Filter attribute to specify multiple tiddlers (optional alternative to tiddler) | - | 
| field | Field to update in the target tiddler | "text" | 
| unit | Unit for the resizer (px, %, em, rem, vh, vw, etc.) | "px" | 
| default | Default value if tiddler doesn't exist (supports calc() expressions) | "200px" or "50%" | 
| min | Minimum value (supports calc() expressions) | "50" or "10" | 
| max | Maximum value (supports calc() expressions) | "800" or "90" | 
| Attribute | Description | Default | 
|---|---|---|
| invert | Invert resize direction: "yes" or "no" | "no" | 
| live | Update target element in real-time: "yes" or "no" | "no" | 
| position | Position calculation: "absolute" or "relative" | "absolute" | 
| mode | Resize mode: "single" or "multiple" | "single" | 
| Attribute | Description | Default | 
|---|---|---|
| selector | CSS selector for target DOM element(s) | - | 
| element | Target relative element: "parent", "parent.parent", "previousSibling", "nextSibling" | - | 
| property | CSS property to modify | "width" or "height" | 
| aspectRatio | Maintain aspect ratio for live DOM manipulation only (e.g., "16:9" or "1.5") | - | 
| Attribute | Description | 
|---|---|
| actions | Action string to execute on value change | 
| onBeforeResizeStart | Actions to execute before resize starts (useful for setup) | 
| onResizeStart | Actions to execute when resize starts | 
| onResize | Actions to execute during resize | 
| onResizeEnd | Actions to execute when resize ends | 
| dblClickActions | Custom actions to execute on double-click (overrides reset behavior) | 
The following variables are available within action strings:
| Variable | Description | Available In | 
|---|---|---|
| <<tv-action-value>> | The numeric value in the widget's unit | All actions | 
| <<tv-action-value-pixels>> | The value in pixels (always pixels regardless of unit) | All actions | 
| <<tv-action-formatted-value>> | The value with unit (e.g., "350px", "50%") | All actions | 
| <<tv-action-direction>> | The resize direction ("horizontal" or "vertical") | All actions | 
| <<tv-action-property>> | The CSS property being modified | All actions | 
| <<tv-action-handle-size>> | The computed size of the resize handle in pixels | All actions | 
| <<tv-action-parent-size>> | The parent container width (horizontal) or height (vertical) in pixels | All actions | 
| <<tv-action-delta-x>> | The horizontal mouse movement delta | onResizeonly | 
| <<tv-action-delta-y>> | The vertical mouse movement delta | onResizeonly | 
| Attribute | Description | Default | 
|---|---|---|
| class | Additional CSS classes for the resizer | "" | 
| handlePosition | Position of resize handle: "before", "after", "overlay" | "after" | 
| handleStyle | Visual style of the handle: "solid", "dots", "lines", "chevron", "grip" | "solid" | 
| disable | Disable the resizer: "yes" or "no" | "no" | 
| visiblePortion | Calculate resize based only on visible portion when element is clipped: "yes" or "no" | "no" | 
| Attribute | Description | Default | 
|---|---|---|
| resetTo | What value to reset to on double-click: "default", "min", "max", "custom" (ignored if dblClickActionsis set) | "default" | 
| resetValue | Custom value to reset to when resetTo="custom" (ignored if dblClickActionsis set) | - | 
| smoothReset | Animate the reset transition: "yes" or "no" (ignored if dblClickActionsis set) | "yes" | 
| onReset | Action string to execute when resizer is reset (ignored if dblClickActionsis set) | - | 
| Attribute | Description | Default | 
|---|---|---|
| hapticFeedback | Enable haptic feedback on touch devices: "yes" or "no" | "yes" | 
<$resizer
  direction="horizontal"
  tiddler="[tag[layout-metrics]]"
  min="100px"
  max="calc(100% - 200px)"
/><$resizer
  direction="horizontal"
  filter="$:/metrics/storyright $:/metrics/storywidth $:/metrics/tiddlerwidth"
  min="300px"
  max="calc(100vw - 350px)"
/><!-- Percentage-based resizing -->
<$resizer
  direction="vertical"
  tiddler="$:/config/header/height"
  unit="%"
  min="5%"
  max="50%"
  default="20%"
/>
<!-- Using viewport units -->
<$resizer
  direction="horizontal"
  tiddler="$:/config/panel/width"
  unit="vw"
  min="20vw"
  max="80vw"
  default="50vw"
/><$resizer
  direction="horizontal"
  tiddler="$:/state/sidebar/width"
  actions="""
    <$action-setfield $tiddler="$:/state/sidebar/visible" text="yes"/>
  """
  onResizeEnd="""
    <$action-log message="Resize completed" value=<<value>>/>
  """
/><$resizer
  direction="horizontal"
  selector=".tc-sidebar"
  property="width"
  tiddler="$:/config/sidebar/width"
  live="yes"
/>The disable attribute allows you to temporarily disable the resizer functionality:
<!-- Disable resizer based on condition -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  disable={{{ [{$:/state/edit-mode}match[yes]then[yes]else[no]] }}}
/>
<!-- Always disabled resizer -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  disable="yes"
/>When disabled:
- The resizer handle remains visible but is non-interactive
- The class tc-resizer-disabledis added for styling
- No resize events or actions are triggered
- The data-disabled="true"attribute is set on the DOM element
Double-click any resizer handle to reset it to a specified value:
<!-- Reset to default value -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  default="300px"
  resetTo="default"
/>
<!-- Reset to minimum value -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  min="200px"
  resetTo="min"
/>
<!-- Reset to custom value with action -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  resetTo="custom"
  resetValue="400px"
  onReset="""
    <$action-log message="Panel reset to 400px"/>
  """
/>
<!-- Disable smooth animation on reset -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  smoothReset="no"
/>
<!-- Custom double-click actions (overrides reset behavior) -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  dblClickActions="""
    <$action-sendmessage $message="tm-modal" $param="$:/core/ui/ControlPanel/Settings"/>
    <$action-log message="Panel width on double-click" value=<<tv-action-value>>/>
  """
/>When using dblClickActions, the following variables are available:
- <<tv-action-value>>- The current value with unit
- <<tv-action-value-pixels>>- The current value in pixels
- <<tv-action-direction>>- The resize direction
- <<tv-action-parent-size>>- The parent container size in pixels
- <<tv-action-handle-size>>- The handle size in pixels
Choose from different visual styles for the resizer handle:
<!-- Default solid bar -->
<$resizer handleStyle="solid" />
<!-- Dots pattern -->
<$resizer handleStyle="dots" />
<!-- Dashed lines -->
<$resizer handleStyle="lines" />
<!-- Chevron arrows (❯❯ for horizontal, ⌄⌄ for vertical) -->
<$resizer handleStyle="chevron" />
<!-- Grip dots (⋮⋮ for horizontal, ⋯⋯ for vertical) -->
<$resizer handleStyle="grip" />The visiblePortion attribute allows the resizer to work correctly with elements that are partially clipped outside the viewport:
<!-- Enable visible portion mode for clipped elements -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  visiblePortion="yes"
/>When enabled, this mode:
- Calculates resize operations based only on the visible portion of the element
- Automatically adjusts the resize ratio when elements extend beyond viewport boundaries
- Ensures consistent resizing behavior for partially visible elements
- Useful for panels that slide off-screen or are clipped by viewport edges
This is particularly helpful when working with:
- Off-canvas navigation panels
- Sliding drawers that extend beyond viewport
- Elements with negative margins or transforms
- Overflow-hidden containers with content outside bounds
The resizer includes enhanced support for touch devices:
<!-- Enable haptic feedback (default) -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  hapticFeedback="yes"
/>
<!-- Disable haptic feedback -->
<$resizer
  direction="horizontal"
  tiddler="$:/state/panel/width"
  hapticFeedback="no"
/>Haptic feedback provides:
- 5ms vibration on touch start (grab)
- 3ms vibration on touch end (release)
- Double pulse (10-50-10ms) on double-click reset
The resizer is used in TiddlyWiki's sidebar implementation:
<$resizer
  class="tc-sidebar-resizer"
  direction="horizontal"
  filter="$:/themes/tiddlywiki/vanilla/metrics/storyright $:/themes/tiddlywiki/vanilla/metrics/storywidth $:/themes/tiddlywiki/vanilla/metrics/tiddlerwidth"
  min={{$:/themes/tiddlywiki/vanilla/metrics/storyminwidth}}
  max={{{ [[calc(100vw - ]addsuffix{$:/themes/tiddlywiki/vanilla/metrics/sidebarminwidth}addsuffix[)]] }}}
  default="350px"
  invert="no"
/>This example demonstrates:
- Multiple tiddlers being resized together
- Dynamic min/max values from tiddlers
- Complex calc() expression for maximum value
- Integration with TiddlyWiki's theme system
The widget supports CSS calc() expressions in min, max, and default values, including special variables:
The following variables can be used within calc() expressions in the min, max, and default attributes:
- handleSize- The computed width/height of the resize handle
- handleWidth- Alias for handleSize
- handleHeight- Alias for handleSize
These variables are automatically replaced with the actual pixel size of the resize handle when the calc() expression is evaluated.
<!-- Leave 350px for sidebar -->
<$resizer
  max="calc(100% - 350px)"
/>
<!-- Use viewport width -->
<$resizer
  max="calc(100vw - 400px)"
/>
<!-- Complex calculations -->
<$resizer
  min="calc(20% + 100px)"
  max="calc(80% - 50px)"
/>
<!-- Dynamic default value based on viewport -->
<$resizer
  default="calc(50vw - 100px)"
  min="200px"
  max="800px"
/>
<!-- Responsive default with fallback -->
<$resizer
  default="calc(100% / 3)"
  min="calc(100% / 6)"
  max="calc(100% / 2)"
/>
<!-- Using handle size in calculations -->
<$resizer
  min="calc(handleSize + 20px)"
  max="calc(100% - handleSize)"
  default="calc(50% - handleSize / 2)"
/>
<!-- Account for handle in panel layouts -->
<$resizer
  direction="horizontal"
  max="calc(100vw - 400px - handleWidth)"
  min="calc(200px + handleWidth)"
/>The widget intelligently handles mixed units:
- Tiddlers can store values in any unit (e.g., "2.5rem", "50vh", "300px")
- Internal calculations are performed in pixels for consistency
- Values are converted back to the original unit when saved
- Maintains precision with appropriate decimal places per unit type
When resizing multiple tiddlers:
- If ANY tiddler would exceed min/max limits, NO tiddlers are updated
- This preserves relative relationships between tiddler values
- All tiddlers move together within the defined constraints
The widget creates a div element with the class tc-resizer plus any additional classes specified. You can style it with CSS:
.tc-resizer {
  cursor: ew-resize; /* or ns-resize for vertical */
  width: 5px;
  background: #ccc;
  position: relative;
}
.tc-resizer:hover {
  background: #999;
}
.tc-resizer-active {
  background: #666;
}
.tc-resizer-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}During resize operations:
- .tc-resizingclass is added to the body element
- .tc-resizer-activeclass is added to the active resizer
- .tc-resize-overlayoverlay captures pointer events
The resizer plugin includes several pre-built layout procedures that make it easy to create common split-panel layouts:
Creates a horizontally split layout with a resizable divider between left and right panels.
<<horizontal-split-panel
  leftContent:"Content for left panel"
  rightContent:"Content for right panel"
  width:"50%"
  minWidth:"100px"
  maxWidth:"80%"
  stateTiddler:"$:/state/hsplit/width"
  class:"my-panel"
  leftClass:"left-panel-class"
  rightClass:"right-panel-class"
  splitterClass:"splitter-class"
>>| Parameter | Description | Default | 
|---|---|---|
| leftContent | Content for the left panel (variable or tiddler name) | "" | 
| rightContent | Content for the right panel (variable or tiddler name) | "" | 
| width | Initial width of the left panel | "50%" | 
| minHeight | Minimum height of the panel container | "100%" | 
| minWidth | Minimum width of the left panel | "100px" | 
| maxWidth | Maximum width of the left panel | "80%" | 
| stateTiddler | Tiddler to store the current width | "$:/state/hsplit/width" | 
| class | Additional CSS classes for the container | "" | 
| leftClass | Additional CSS classes for the left panel | "" | 
| rightClass | Additional CSS classes for the right panel | "" | 
| splitterClass | Additional CSS classes for the splitter | "" | 
Creates a vertically split layout with a resizable divider between top and bottom panels.
<<vertical-split-panel
  topContent:"Content for top panel"
  bottomContent:"Content for bottom panel"
  height:"50%"
  panelHeight:"100%"
  minHeight:"100px"
  maxHeight:"80%"
  stateTiddler:"$:/state/vsplit/height"
  class:"my-panel"
  topClass:"top-panel-class"
  bottomClass:"bottom-panel-class"
  splitterClass:"splitter-class"
>>| Parameter | Description | Default | 
|---|---|---|
| topContent | Content for the top panel (variable or tiddler name) | "" | 
| bottomContent | Content for the bottom panel (variable or tiddler name) | "" | 
| panelHeight | Height of the entire panel container | "100%" | 
| height | Initial height of the top panel | "50%" | 
| minHeight | Minimum height of the top panel | "100px" | 
| maxHeight | Maximum height of the top panel | "80%" | 
| stateTiddler | Tiddler to store the current height | "$:/state/vsplit/height" | 
| class | Additional CSS classes for the container | "" | 
| topClass | Additional CSS classes for the top panel | "" | 
| bottomClass | Additional CSS classes for the bottom panel | "" | 
| splitterClass | Additional CSS classes for the splitter | "" | 
Creates a three-column layout with resizable left and right panels, and a flexible center panel.
<<three-column-panels
  leftContent:"Left panel content"
  centerContent:"Center panel content"
  rightContent:"Right panel content"
  leftWidth:"200px"
  rightWidth:"200px"
  minWidth:"150px"
  maxWidth:"400px"
  minHeight:"100%"
  leftStateTiddler:"$:/state/three-col/left"
  rightStateTiddler:"$:/state/three-col/right"
  class:"my-three-col"
>>| Parameter | Description | Default | 
|---|---|---|
| leftContent | Content for the left panel (variable or tiddler name) | "" | 
| centerContent | Content for the center panel (variable or tiddler name) | "" | 
| rightContent | Content for the right panel (variable or tiddler name) | "" | 
| leftWidth | Initial width of the left panel | "200px" | 
| rightWidth | Initial width of the right panel | "200px" | 
| minWidth | Minimum width for side panels | "150px" | 
| maxWidth | Maximum width for side panels | "400px" | 
| minHeight | Minimum height of the panel container | "100%" | 
| leftStateTiddler | Tiddler to store the left panel width | "$:/state/three-col/left" | 
| rightStateTiddler | Tiddler to store the right panel width | "$:/state/three-col/right" | 
| class | Additional CSS classes for the container | "" | 
Note: The center panel automatically adjusts its width based on the left and right panel sizes, with constraints to ensure all panels remain visible.
Creates a master-detail layout where the master panel can be collapsed to save space.
<<collapsible-master-detail-panel
  masterContent:"Master panel content"
  detailContent:"Detail panel content"
  collapsed:"no"
  size:"300px"
  minSize:"200px"
  maxSize:"500px"
  minHeight:"100%"
  stateTiddler:"$:/state/cmd/size"
  collapseStateTiddler:"$:/state/cmd/collapsed"
  class:"my-master-detail"
>>| Parameter | Description | Default | 
|---|---|---|
| masterContent | Content for the master panel (variable or tiddler name) | "" | 
| detailContent | Content for the detail panel (variable or tiddler name) | "" | 
| collapsed | Initial collapsed state ("yes" or "no") | "no" | 
| size | Initial width of the master panel | "300px" | 
| minSize | Minimum width of the master panel | "200px" | 
| maxSize | Maximum width of the master panel | "500px" | 
| minHeight | Minimum height of the panel container | "100%" | 
| stateTiddler | Tiddler to store the master panel width | "$:/state/cmd/size" | 
| collapseStateTiddler | Tiddler to store the collapsed state | "$:/state/cmd/collapsed" | 
| class | Additional CSS classes for the container | "" | 
Features:
- Collapse/expand buttons integrated into the master panel
- Detail panel automatically expands when master panel is collapsed
- State persistence for both size and collapse state
The plugin includes a mediaquery filter operator that allows you to evaluate CSS media queries within TiddlyWiki filters. This is particularly useful for creating responsive layouts and conditional content.
[mediaquery<media-query>]
<!-- Show content only on mobile devices -->
<%if [mediaquery[(max-width: 768px)]] %>
  This content only appears on mobile devices
<% endif %>
<!-- Show different content for touch vs mouse devices -->
<%if [mediaquery[(pointer: coarse)]] %>
  <div class="touch-interface">
    Touch-optimized interface with larger buttons
  </div>
<% else %>
  <div class="mouse-interface">
    Mouse-optimized interface with hover states
  </div>
<% endif %>
<!-- Responsive layout based on screen size -->
<%if [mediaquery[(min-width: 1024px)]] %>
  <<three-column-panels
    leftContent:"Navigation"
    centerContent:"Main Content"
    rightContent:"Sidebar"
  >>
<% else %>
  <<vertical-split-panel
    topContent:"Navigation"
    bottomContent:"Main Content"
  >>
<% endif %>
<!-- Dark mode support -->
<%if [mediaquery[(prefers-color-scheme: dark)]] %>
  <style>
    .my-component { background: #1a1a1a; color: #ffffff; }
  </style>
<% endif %>
<!-- Responsive resizer configuration -->
<$let handleWidth={{{ [mediaquery[(pointer: coarse)]then[40px]else[10px]] }}}>
  <$resizer
    direction="horizontal"
    tiddler="$:/state/panel-width"
    default=<<handleWidth>>
  />
</$let>
<!-- Disable animations for users who prefer reduced motion -->
<%if [mediaquery[(prefers-reduced-motion: reduce)]] %>
  <style>
    * { animation: none !important; transition: none !important; }
  </style>
<% endif %>- Reactive Updates: Automatically refreshes when media query state changes (e.g., window resize, device rotation)
- Browser-Only: Returns empty results when running in Node.js
- Error Handling: Invalid media queries return empty results
- Negation Support: Use !mediaqueryto invert the condition
- (max-width: 768px)- Mobile devices
- (min-width: 769px)- Tablets and desktops
- (pointer: coarse)- Touch devices
- (pointer: fine)- Mouse/trackpad devices
- (prefers-color-scheme: dark)- Dark mode preference
- (orientation: portrait)- Portrait orientation
- (orientation: landscape)- Landscape orientation
- (prefers-reduced-motion: reduce)- Reduced motion preference
- Modern browsers with ES5 support
- Touch devices via pointer events
- MediaQueryList API support for reactive media queries
- Fallback handling for older viewport unit implementations
- Cross-browser window object detection
Contributions are welcome! Please feel free to submit a Pull Request.
This plugin is released under the MIT License. See the LICENSE file for details.
Created for the TiddlyWiki community by BTC.