Skip to content

proposal: support data-* attributes in DOM JSX - #933

Draft
anmonteiro wants to merge 1 commit into
mainfrom
anmonteiro/proposal-data-attrs
Draft

anmonteiro wants to merge 1 commit into
mainfrom
anmonteiro/proposal-data-attrs

Conversation

@anmonteiro

Copy link
Copy Markdown
Member

fixes #230

Proposes supporting custom data attributes directly in JSX:

let element = <div className="item" dataTestId="item" />;

The PPX generates a local @mel.obj constructor containing the ordinary props and data attributes supplied at that location. For the props above, the expansion is roughly:

let module Props = struct
  external make :
    check:((className:'a -> unit -> ReactDOM.domProps) [@mel.ignore]) ->
    className:'a ->
    dataTestId:(string [@mel.as "data-test-id"]) ->
    unit -> ReactDOM.domProps
    = "" [@@mel.obj]
end in
Props.make
  ~check:(fun ~className () ->
    ReactDOM.domProps ~className ())
  ~className:"item"
  ~dataTestId:"item"
  ()

The callback checks ordinary props against ReactDOM.domProps. Here, that constrains className to string; the shared 'a applies the same constraint to the actual constructor argument. This preserves existing type checks without
maintaining a second copy of the DOM prop types in the PPX.

With [@mel.ignore], Melange erases the ignored callback and compiles the original JSX to:

const element = JsxRuntime.jsx("div", {
  className: "item",
  "data-test-id": "item"
});

Each JSX expression gets its own constructor, so different elements can supply different sets of attributes. There is no runtime merge, and each prop expression is evaluated once.

Data attribute values are strings, and optional attributes are omitted when their value is None. Existing property aliases such as type_ and ariaLabel are preserved. This only applies to DOM elements -- user-defined components continue to use their declared props.

related to melange-re/melange#2004.

@davesnx

davesnx commented Sep 28, 2026

Copy link
Copy Markdown
Member

Note on digits: server-reason-react's ppx converts data attribute names differently when a digit follows a letter.

JSX name this PR server-reason-react
dataVersion2 data-version2 data-version-2

server-reason-react puts a dash before every character that is unchanged by Char.uppercase_ascii, and digits are unchanged, so each digit gets a dash. This PR puts a dash only before A–Z.

If one component is rendered on the server with server-reason-react and hydrated with reason-react, the attribute names will not match. We should agree on one rule. data-version2 looks like the better choice to me (it matches how dataset maps data-version2 to version2), so we can change server-reason-react to match this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow data-* in DOM elements

2 participants