Enhanced attribute management for Reflex-DOM applications.
reflex-dom-attrs provides a composable, type-safe system for managing HTML attributes in Reflex-DOM applications. It offers a clean DSL for working with both static and dynamic attributes while optimizing performance by avoiding unnecessary Dynamic creation.
- Composable attributes via
Semigroup/Monoidinstances - Performance optimized - uses static elements when possible
- Type-safe attribute builders with the
~:operator - Polymorphic type classes for classes, styles, and attributes
- Self-referential attributes that can access the created element
- Enhanced element functions with built-in attribute support
Add to your .cabal file:
build-depends: reflex-dom-attrs{-# LANGUAGE OverloadedStrings #-}
import Reflex.Dom.Attrs
-- Simple static attributes
myButton :: Widget t m => m (Event t ())
myButton = elButton_
[ "class" ~: "btn btn-primary"
, "style" ~: ["padding" ~:: "10px", "color" ~:: "white"]
, "data-id" ~: "submit-btn"
] $ text "Click me!"
-- Dynamic attributes
dynamicDiv :: Widget t m => Dynamic t Text -> m ()
dynamicDiv dynClass = elDiv
[ "class" ~: dynClass
, "style" ~: ["display" ~:: "flex"]
] $ text "Dynamic content"
-- Composing attributes
baseAttrs, extraAttrs :: Attrs t m
baseAttrs = "class" ~: "widget" <> "data-role" ~: "container"
extraAttrs = "class" ~: "enhanced" <> "style" ~: ["margin" ~:: "5px"]
combined :: Widget t m => m ()
combined = elDiv [baseAttrs, extraAttrs] $ text "Composed"
-- Results in: class="widget enhanced" data-role="container" style="margin:5px"The central type that represents a collection of HTML attributes:
data Attrs t m = Attrs
{ attrs_class :: Text -- Static classes
, attrs_style :: Map Text Text -- Static styles
, attrs_attrs :: Map Text Text -- Static attributes
, attrs_dynClass :: Maybe (Dynamic t Text) -- Dynamic classes
, attrs_dynStyle :: Maybe (Dynamic t (Map Text Text)) -- Dynamic styles
, attrs_dynAttrs :: Maybe (Dynamic t (Map Text Text)) -- Dynamic attributes
, attrs_self :: Maybe (El t m -> m [Attrs t m]) -- Self-referential
}The primary way to create attributes:
-- Text attributes
"id" ~: "my-element"
"class" ~: "container"
-- Style attributes (using lists)
"style" ~: ["color" ~:: "red", "padding" ~:: "10px"]
-- Dynamic attributes
"class" ~: dynClass
"data-value" ~: dynValuePolymorphic interfaces for specific attribute types:
-- Classes
_class_ "my-class" -- Static
_class_ dynClass -- Dynamic
-- Styles
_style_ $ Map.fromList [("color", "blue")] -- Static
_style_ dynStyles -- Dynamic
-- Generic attributes
_attrs_ $ Map.fromList [("data-id", "123")] -- Static
_attrs_ dynAttrs -- DynamicAll standard HTML elements are supported with attribute-aware variants:
-- Basic elements
elDiv :: [Attrs t m] -> m a -> m a
elSpan :: [Attrs t m] -> m a -> m a
elImg :: [Attrs t m] -> m a -> m a
-- Elements returning the DOM element
elDiv' :: [Attrs t m] -> m a -> m (El t m, a)
elSpan' :: [Attrs t m] -> m a -> m (El t m, a)
-- Interactive elements
elButton :: [Attrs t m] -> m a -> m (Event t (), a)
elButton' :: [Attrs t m] -> m a -> m (El t m, Event t (), a)
-- Form elements
elInput :: [Attrs t m] -> InputElConfig t m -> m (InputEl t m)
elTextArea :: [Attrs t m] -> TextAreaElConfig t m -> m (TextAreaEl t m)Attributes that depend on the created element:
selfRefExample :: Widget t m => m ()
selfRefExample = elDiv
[ _self_attrs_ $ \el -> do
-- Access element after creation
performEvent_ $ domEvent Click el $> liftIO (putStrLn "Clicked!")
pure []
] $ text "Click me"Change entire attribute sets dynamically:
conditionalAttrs :: Widget t m => Dynamic t Bool -> m ()
conditionalAttrs isActive = elDiv
[ foldDynAttrs $ isActive <&> \active ->
if active
then ["class" ~: "active", "style" ~: ["color" ~:: "green"]]
else ["class" ~: "inactive", "style" ~: ["color" ~:: "gray"]]
] $ text "Conditional styling"The library automatically optimizes element creation:
- When all attributes are static, uses
elAttrinternally - When any attribute is dynamic, uses
elDynAttr - Avoids creating unnecessary
Dynamicvalues
-- This creates a static element (efficient)
staticEl = elDiv ["class" ~: "static"] $ text "Fast"
-- This creates a dynamic element (when needed)
dynamicEl dynClass = elDiv ["class" ~: dynClass] $ text "Reactive"- Composability First: Attributes combine naturally via
Semigroup - Performance Aware: Static paths optimized automatically
- Type Safety: Leverage Haskell's type system for correctness
- Ergonomic: Clean, intuitive syntax with the
~:operator - Incremental Adoption: Works alongside standard reflex-dom
validatedInput :: Widget t m => m (Dynamic t Text)
validatedInput = do
rec
input <- elInput
[ "class" ~: isValid <&> \valid ->
if valid then "form-control" else "form-control error"
, "placeholder" ~: "Enter email"
] def
let value = _inputElement_value input
isValid = value <&> \v -> '@' `T.elem` v
pure valueresponsiveContainer :: Widget t m => Dynamic t Bool -> m () -> m ()
responsiveContainer isMobile content = elDiv
[ "class" ~: "container"
, "style" ~: isMobile <&> \mobile ->
if mobile
then ["padding" ~:: "5px", "font-size" ~:: "14px"]
else ["padding" ~:: "20px", "font-size" ~:: "16px"]
] contentloadingButton :: Widget t m => Dynamic t Bool -> m (Event t ())
loadingButton isLoading = elButton
[ "class" ~: isLoading <&> \loading ->
if loading then "btn btn-disabled" else "btn btn-primary"
, "disabled" ~: isLoading <&> \loading ->
if loading then "true" else ""
] $ dynText $ isLoading <&> \loading ->
if loading then "Loading..." else "Submit"BSD3 - See LICENSE file for details
Yuri Meister
Contributions are welcome! Please feel free to submit issues or pull requests.