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 | onResize only |
<<tv-action-delta-y>> |
The vertical mouse movement delta | onResize only |
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 dblClickActions is set) |
"default" |
resetValue |
Custom value to reset to when resetTo="custom" (ignored if dblClickActions is set) |
- |
smoothReset |
Animate the reset transition: "yes" or "no" (ignored if dblClickActions is set) |
"yes" |
onReset |
Action string to execute when resizer is reset (ignored if dblClickActions is 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-disabled
is 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 handlehandleWidth
- Alias for handleSizehandleHeight
- 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-resizing
class is added to the body element.tc-resizer-active
class is added to the active resizer.tc-resize-overlay
overlay 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
!mediaquery
to 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.