Changelog
View SourceAll notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.13.1] - 2026-01-08
Added
- Auto-recompile when manifest is empty: Mix compiler now detects empty manifests and automatically triggers
mix compile.elixir --forceto repopulate them - Debug logging for unchanged CSS output in watch mode (
LiveStyle: CSS unchanged, skipping write → path)
Fixed
- Add consistent file locking in
Storage.write/update/clearto prevent race conditions whenprocess_active?is true - Fix
manifest_empty?to check all 7 collections, not just 3 - Add warning log when manifest version mismatch causes data discard
- Fix CSS writer to distinguish read errors from missing files
- Fix watcher debounce to use absolute deadline so non-manifest events don't reset the timer
- Remove redundant file delete in
Storage.clear()(atomic write already overwrites)
[0.13.0] - 2026-01-03
Added
LiveStyle.install_and_run/2for Phoenix endpoint watcher integration- Follows the same pattern as Tailwind and esbuild
- Watches manifest file for changes and regenerates CSS automatically
- Configure in
config/dev.exsunder watchers - See Getting Started guide for setup instructions
Changed
BREAKING: Renamed definition macros for consistency
theme/2→theme_class/2view_transition/2→view_transition_class/2- Both now match their reference forms (
theme_class/1,view_transition_class/1)
BREAKING: Default manifest path changed from
priv/live_style_manifest.etfto_build/live_style/manifest.etf- Manifest is now in dedicated subdirectory for faster file watching
- Automatically cleaned by
mix clean - No longer needs to be gitignored (already in
_build/) - Override with
config :live_style, manifest_path: "custom/path.etf"
Consolidated storage modules into single
LiveStyle.Storagemodule- Removed:
Storage.Adapter,Storage.Cache,Storage.FileAdapter,Storage.IO,Storage.Lock,Storage.Path,Storage.ProcessState,Storage.TableOwner - Simpler architecture with direct file operations and directory-based locking
- Removed:
Fixed
File watcher now detects manifest changes on macOS
- Atomic writes use rename, which triggers
:renamedevents instead of:modified - Watch mode now handles
:renamedand:movedevents in addition to:modified/:created - Added 50ms debouncing to coalesce rapid file events into single rebuild
- Atomic writes use rename, which triggers
Theme variables now use correct CSS variable prefix from config
- Previously theme overrides used hardcoded
--vprefix instead of--x(fromConfig.class_name_prefix()) - This caused themes to define different variables than base vars, breaking theme switching
- Previously theme overrides used hardcoded
StyleX-compatible property merging behavior
defaultcondition now uses just the property name (e.g.,color) instead ofcolor::default- Each property key is completely independent - only exact key matches conflict
colorandcolor:::hoverare separate keys that coexist
[0.11.1] - 2026-01-02
Fixed
- Updated all documentation examples to use
MyAppWebnamespace (Phoenix convention) - Fixed
LiveStyle.CSS.Propertyreferences toLiveStyle.PropertyTypein design-tokens guide - Updated default output path to
priv/static/assets/css/live.css(Phoenix-compatible structure) - Updated watcher configuration for development
- Fixed benchmark file reference to
LiveStyle.Compiler.CSS
Documentation
- Improved Getting Started guide with esbuild CSS configuration
- Added development watcher setup instructions
- Updated configuration guide with Phoenix-compatible paths
- Standardized all code examples to keyword list syntax
[0.11.0] - 2025-01-01
Changed
BREAKING: Simplified API by removing
css_prefix from all macros:css_class/2→class/2css_vars/2→vars/1(namespace is now the module)css_consts/2→consts/1(namespace is now the module)css_keyframes/2→keyframes/2css_theme/3→theme/2(namespace is now the module)css_view_transition/2→view_transition_class/2css_position_try/2→position_try/2css_var/1→var/1(2-tuple{Module, :name}instead of 3-tuple)css_const/1→const/1(2-tuple{Module, :name}instead of 3-tuple)
BREAKING: Unified module system - use
use LiveStyleinstead of:use LiveStyle.Sheet(removed)use LiveStyle.Tokens(removed)
BREAKING: Token references now use 2-tuples instead of 3-tuples:
# Before css_var({MyApp.Tokens, :colors, :primary}) css_const({MyApp.Tokens, :spacing, :md}) # After var({MyApp.Colors, :primary}) const({MyApp.Spacing, :md})
Internal
- Major codebase restructuring for better organization:
- Moved compiler-related code into
lib/live_style/compiler/ - Renamed
LiveStyle.Data→LiveStyle.PropertyMetadata - Renamed
LiveStyle.Types→LiveStyle.PropertyType - Renamed
LiveStyle.Value→LiveStyle.CSSValue - Consolidated manifest and utility modules
- Moved compiler-related code into
- Added comprehensive snapshot tests for CSS output verification
- Added
LiveStyle.Registrymacro for DRY manifest registration - Improved conditional detection for magic string keys
[0.10.0] - 2025-12-23
Removed
- Removed nested at-rule map syntax in
class/2(top-level keys like"@media (...)" => %{...}); use per-property conditional values instead.
[0.9.0] - 2024-12-21
Added
css/2macro with:styleoption for merging additional inline styles:<div {css([:card], style: [ view_transition_class: css_view_transition(:card), view_transition_name: "card-#{@id}" ])}>Comprehensive documentation for Phoenix LiveView View Transitions integration:
- Complete JavaScript adapter code (
createViewTransitionDom) - Reusable
ViewTransitioncomponent with colocated hook - Step-by-step integration guide
- Key insights for correct timing and element structure
- Complete JavaScript adapter code (
Documentation for CSS Scroll-Driven Animations:
- Scroll progress timelines (
animation-timeline: scroll()) - View progress timelines (
animation-timeline: view()) - Named view timelines for parallax effects
- Horizontal scroll progress with named scroll timelines
- Animation range control
- Scroll progress timelines (
LiveStyle.Devmodule with development helpers for inspecting styles:class_info/2- Returns detailed info about a class (CSS, properties, values)list/1,2- Lists all class names in a module (with :static/:dynamic filtering)diff/2- Shows how multiple classes merge with property-level detailcss/2- Returns raw CSS output for classestokens/1- Shows all tokens defined in a modulepp/2,pp_list/1- Pretty-print helpers for console output
mix live_style.audittask to find potentially unused class definitions:- Scans codebase for
css_class/2definitions - Searches for references in
.ex,.exs, and.heexfiles - Reports classes with no apparent references
- Supports
--format jsonfor tooling integration
- Scans codebase for
mix live_style.inspecttask to inspect class definitions from CLI:- Shows generated CSS and property breakdown
- Supports inspecting multiple classes with merged result
--cssflag for raw CSS output
Changed
css_view_transition/1macro now returns compile-time values instead of runtime lookups
Fixed
- View transition class references are now resolved at compile time for better performance
[0.7.0] - 2024-12-20
Changed
BREAKING: CSS layers behavior now matches StyleX defaults:
use_css_layers: false(default) - Uses:not(#\#)selector hack for specificity (StyleX default)use_css_layers: true- Groups rules by priority in@layer priorityNblocks (StyleXuseLayers: true)- Removed
use_priority_layersconfig option (no longer needed)
BREAKING: Renamed shorthand behavior config and modes:
- Config key:
shorthand_strategy→shorthand_behavior :keep_shorthands→:accept_shorthands:reject_shorthands→:forbid_shorthands:expand_to_longhands→:flatten_shorthands
- Config key:
BREAKING: Renamed shorthand modules:
LiveStyle.Shorthand.Strategy→LiveStyle.ShorthandBehaviorLiveStyle.Shorthand.Strategy.KeepShorthands→LiveStyle.ShorthandBehavior.AcceptShorthandsLiveStyle.Shorthand.Strategy.ExpandToLonghands→LiveStyle.ShorthandBehavior.FlattenShorthandsLiveStyle.Shorthand.Strategy.RejectShorthands→LiveStyle.ShorthandBehavior.ForbidShorthands
BREAKING: Renamed config function:
shorthand_strategy/0→LiveStyle.Config.shorthand_behavior/0
Renamed internal terminology from "null" to "nil" (Elixir idiom):
%{null: true}→%{unset: true}in atomic class maps:__null__→:__unset__sentinel atom
CSS variable prefix now uses configurable
class_name_prefixinstead of hardcoded--x-
Added
CSS property validation with "did you mean?" suggestions for typos
validate_properties: trueconfig option (default: true)unknown_property_level: :warn-:error,:warn, or:ignorevendor_prefix_level: :warn- warns when vendor prefixes are used unnecessarilydeprecated_property_level: :warn- warns when deprecated properties are useddeprecated?: &MyApp.CSS.deprecated?/1- configurable deprecation check function
Configurable CSS prefixing via
prefix_cssconfig optionprefix_css: &MyApp.CSS.prefix/2- function to add vendor prefixesLiveStyle.Config.apply_prefix_css/2- applies configured prefixer
Automatic selector prefixing for pseudo-elements (e.g.,
::thumb,::placeholder)- Generates vendor-prefixed variants automatically
Fixed
- Validation warnings now appear on recompile (file/line info threaded through call chain)
- RTL type spec now correctly accepts
nilfor selector_suffix parameter - Support for comma-separated keyframe keys like
"0%, 100%"
Internal
- Extracted SRP-focused modules from monolithic
LiveStyle.ClassandLiveStyle.CSS - Optimized
Property.Validation.known?with pattern matching (~7% faster) - Optimized
Selector.Prefixer.prefixwith binary slicing (~43% faster) - Extracted
LiveStyle.Hash.Murmurmodule for MurmurHash3 implementation - Removed specific package references from documentation (now uses generic examples)
[0.6.2] - 2024-12-19
Changed
BREAKING: Renamed all macros to use
css_prefix for consistency with StyleX naming:style/2→css_class/2defvars/2→css_vars/2defconsts/2→css_consts/2defkeyframes/2→css_keyframes/2keyframes/1→css_keyframes/1(reference form)var/1→css_var/1const/1→css_const/1create_theme/3→css_theme/3position_try/1→css_position_try/1view_transition/2→css_view_transition/2view_transition_class/1→css_view_transition/1(reference form)
BREAKING: Moved tooling functions out of
LiveStyle:- run/2 → LiveStyle.Compiler.run/2
- install_and_run/2 → LiveStyle.Compiler.Runner.install_and_run/2
- write_css/1 → LiveStyle.Compiler.Writer.write_css/1
Update your Phoenix watcher config:
# Before watchers: [live_style: {LiveStyle, :install_and_run, [:default, ~w(--watch)]}] # After watchers: [live_style: {LiveStyle.Compiler, :run, [:default, ~w(--watch)]}]Removed
manifest_path/0,style_resolution/0,output_path/0,config_for!/1delegates fromLiveStyleConfig functions now accessed via
LiveStyle.Configmodule directly
Added
LiveStyle.Compilermodule for all tooling/compilation functions
[0.6.0] - 2024-12-17
Changed
- BREAKING: Default shorthand behavior changed to
:accept_shorthandsfor more intuitive CSS behavior (last style wins) - Simplified
props/1API - now only accepts a single value or a list (removed variadicprops/2-5)
Added
LiveStyle.ShorthandBehaviorbehaviour for custom shorthand handling strategiesLiveStyle.ShorthandBehavior.AcceptShorthands,FlattenShorthands,ForbidShorthandsimplementationsLiveStyle.Storagemodule for file-based manifest storageLiveStyle.Configmodule for unified configuration management with per-process overrides- LiveStyle.Compiler.Writer.write_css/1 function for writing CSS with change detection
Removed
- Removed legacy backward compatibility code for old style reference formats
- Removed unused variadic
props/2-5functions
Internal
- All tests now run with
async: trueusing in-memory storage - Deduplicated code across compiler, watcher, and style resolution modules
- Improved test isolation with
LiveStyle.TestCaseandLiveStyle.TestHelper
[0.5.0] - 2024-12-17
Changed
BREAKING:
css_keyframes/1now takes only frames and returns the generated name (matching StyleX API)# Before (0.4.x) keyframes :spin, from: [...], to: [...] style :spinner, animation_name: :spin # After (0.5.0+) css_keyframes :spin, from: [...], to: [...] css_class :spinner, animation_name: css_keyframes(:spin)BREAKING:
css_view_transition/1now takes only styles and returns the generated class nameKeyframe names now use
x<hash>-Bformat (matching StyleX) instead ofk<hash>
Added
css_position_try/2macro for CSS Anchor Positioning (@position-tryat-rules)- Creates fallback positioning options for anchor-positioned elements
- Returns a dashed-ident string (e.g.,
"--x1a2b3c4") for use withposition_try_fallbacks - Validates that only allowed properties are used (position, inset, margin, size, self-alignment)
- Supports RTL/LTR transformations for logical properties
[0.4.1] - 2024-12-17
Added
- Tuple list syntax support for computed keys as an alternative to map syntax:
# Now you can use tuple lists with computed keys css_class :responsive, font_size: [ {:default, "1rem"}, {css_const({MyApp.Tokens, :breakpoint, :lg}), "1.5rem"} ]
Fixed
- CI now only checks formatting on Elixir 1.17 to avoid formatter version differences
[0.4.0] - 2024-12-17
Added
- Keyword list syntax support for all macros as an alternative to map syntax
normalize_to_map/1helper function for recursively converting keyword lists to maps- Dedicated test file for keyword syntax coverage
Changed
- All style macros now accept both map syntax (
%{key: value}) and keyword list syntax (key: value) - Documentation updated with keyword list examples throughout
Notes
- Keyword list syntax is more idiomatic Elixir and recommended for most use cases
- For computed keys, use either map syntax with
=>or tuple list syntax[{key, value}]
[0.3.0] - 2024-12-17
Added
LiveStyle.ViewTransitionsmodule for CSS View Transitions API support:css_view_transition/2macro for defining transitions by name pattern- Automatic keyframe name resolution in view transition styles
- Support for
:old,:new,:group,:image_pairpseudo-element keys - Support for
:only-childvariants (:old_only_child,:new_only_child, etc.) - Media query conditions (e.g.,
prefers-reduced-motion) - Compile-time validation for keyframe references in
animation_name
Changed
css_keyframes/2now defines a function that returns the hashed keyframe name- Allows keyframes to be used in both styles and view transitions
- Example:
MyApp.Tokens.spin()returns"x1a2b3c4-B"
[0.2.0] - 2024-12-17
Added
LiveStyle.Whenmodule with contextual selectors (inspired by StyleX'sstylex.when.*):ancestor/1,2- style when ancestor has pseudo-statedescendant/1,2- style when descendant has pseudo-statesibling_before/1,2- style when preceding sibling has pseudo-statesibling_after/1,2- style when following sibling has pseudo-stateany_sibling/1,2- style when any sibling has pseudo-state
LiveStyle.default_marker/0- returns the default marker class for contextual selectorsLiveStyle.marker/1- creates unique marker classes for custom contexts- Nested pseudo-class conditions - combine selectors like
:nth-child(2):where(.marker:hover *)
[0.1.0] - 2024-12-16
Added
- Initial release of LiveStyle
css_class/2macro for declaring named styles with CSS declarationscss_keyframes/2macro for defining CSS animationscss_var/1macro for referencing CSS custom propertiesfallback/1macro for CSS fallback valuescss_vars/2macro for defining CSS custom properties (design tokens)css_consts/2macro for defining compile-time constantscss_theme/3macro for creating scoped theme overridesLiveStyle.Typesmodule with type helpers for CSS@propertyrules:color/1,length/1,angle/1,integer/1,number/1,time/1,percentage/1
__include__key for style composition (external modules and self-references)- StyleX-inspired condition-in-value syntax for pseudo-classes and media queries
- Pseudo-element support (
::before,::after, etc.) - Atomic CSS generation with deterministic class name hashing
- CSS
@layersupport for predictable specificity - Mix compiler (
Mix.Tasks.Compile.LiveStyle) for automatic CSS generation - Mix task (
mix live_style.gen.css) for manual CSS generation - Development watcher (
LiveStyle.Watcher) for hot reloading - CSS variable reference validation at compile time
- File-based manifest locking for parallel compilation safety
- Configurable
output_pathfor CSS file location - Configurable
storagefor manifest storage backend