diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3cb7ec6a7c..41383dfe4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,9 @@
 
 # 12.0.0-alpha.12 (Unreleased)
 
+#### :house: Internal
+- Better representation of JSX in AST . https://github.com/rescript-lang/rescript/pull/7286
+
 # 12.0.0-alpha.11
 
 #### :bug: Bug fix
diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml
index 79044efad8..e2d4a51f46 100644
--- a/analysis/src/CompletionFrontEnd.ml
+++ b/analysis/src/CompletionFrontEnd.ml
@@ -1233,8 +1233,6 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
                          then ValueOrField
                          else Value);
                     }))
-        | Pexp_construct ({txt = Lident ("::" | "()")}, _) ->
-          (* Ignore list expressions, used in JSX, unit, and more *) ()
         | Pexp_construct (lid, eOpt) -> (
           let lidPath = flattenLidCheckDot lid in
           if debug then
@@ -1325,10 +1323,29 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
                         inJsx = !inJsxContext;
                       }))
             | None -> ())
-        | Pexp_apply {funct = {pexp_desc = Pexp_ident compName}; args}
-          when Res_parsetree_viewer.is_jsx_expression expr ->
+        | Pexp_jsx_element
+            ( Jsx_unary_element
+                {
+                  jsx_unary_element_tag_name = compName;
+                  jsx_unary_element_props = props;
+                }
+            | Jsx_container_element
+                {
+                  jsx_container_element_tag_name_start = compName;
+                  jsx_container_element_props = props;
+                } ) ->
           inJsxContext := true;
-          let jsxProps = CompletionJsx.extractJsxProps ~compName ~args in
+          let children =
+            match expr.pexp_desc with
+            | Pexp_jsx_element
+                (Jsx_container_element
+                   {jsx_container_element_children = children}) ->
+              children
+            | _ -> JSXChildrenItems []
+          in
+          let jsxProps =
+            CompletionJsx.extractJsxProps ~compName ~props ~children
+          in
           let compNamePath = flattenLidCheckDot ~jsx:true compName in
           if debug then
             Printf.printf "JSX <%s:%s %s> _children:%s\n"
@@ -1345,10 +1362,21 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
               | None -> "None"
               | Some childrenPosStart -> Pos.toString childrenPosStart);
           let jsxCompletable =
-            CompletionJsx.findJsxPropsCompletable ~jsxProps
-              ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor
-              ~posAfterCompName:(Loc.end_ compName.loc)
-              ~firstCharBeforeCursorNoWhite ~charAtCursor
+            match expr.pexp_desc with
+            | Pexp_jsx_element
+                (Jsx_container_element
+                   {
+                     jsx_container_element_closing_tag = None;
+                     jsx_container_element_children =
+                       JSXChildrenSpreading _ | JSXChildrenItems (_ :: _);
+                   }) ->
+              (* This is a weird edge case where there is no closing tag but there are children *)
+              None
+            | _ ->
+              CompletionJsx.findJsxPropsCompletable ~jsxProps
+                ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor
+                ~posAfterCompName:(Loc.end_ compName.loc)
+                ~firstCharBeforeCursorNoWhite ~charAtCursor
           in
           if jsxCompletable <> None then setResultOpt jsxCompletable
           else if compName.loc |> Loc.hasPos ~pos:posBeforeCursor then
diff --git a/analysis/src/CompletionJsx.ml b/analysis/src/CompletionJsx.ml
index 5014a665c8..0c49ae0086 100644
--- a/analysis/src/CompletionJsx.ml
+++ b/analysis/src/CompletionJsx.ml
@@ -455,40 +455,39 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor
   in
   loop jsxProps.props
 
-let extractJsxProps ~(compName : Longident.t Location.loc) ~args =
-  let thisCaseShouldNotHappen =
-    {
-      compName = Location.mknoloc (Longident.Lident "");
-      props = [];
-      childrenStart = None;
-    }
+let extractJsxProps ~(compName : Longident.t Location.loc) ~props ~children =
+  let open Parsetree in
+  let childrenStart =
+    match children with
+    | JSXChildrenItems [] -> None
+    | JSXChildrenSpreading child | JSXChildrenItems (child :: _) ->
+      if child.pexp_loc.loc_ghost then None else Some (Loc.start child.pexp_loc)
   in
-  let rec processProps ~acc args =
-    match args with
-    | (Asttypes.Labelled {txt = "children"}, {Parsetree.pexp_loc}) :: _ ->
-      {
-        compName;
-        props = List.rev acc;
-        childrenStart =
-          (if pexp_loc.loc_ghost then None else Some (Loc.start pexp_loc));
-      }
-    | ( (Labelled {txt = s; loc} | Optional {txt = s; loc}),
-        (eProp : Parsetree.expression) )
-      :: rest -> (
-      let namedArgLoc = if loc = Location.none then None else Some loc in
-      match namedArgLoc with
-      | Some loc ->
-        processProps
-          ~acc:
-            ({
-               name = s;
-               posStart = Loc.start loc;
-               posEnd = Loc.end_ loc;
-               exp = eProp;
-             }
-            :: acc)
-          rest
-      | None -> processProps ~acc rest)
-    | _ -> thisCaseShouldNotHappen
+  let props =
+    props
+    |> List.map (function
+         | JSXPropPunning (_, name) ->
+           {
+             name = name.txt;
+             posStart = Loc.start name.loc;
+             posEnd = Loc.end_ name.loc;
+             exp =
+               Ast_helper.Exp.ident ~loc:name.loc
+                 {txt = Longident.Lident name.txt; loc = name.loc};
+           }
+         | JSXPropValue (name, _, value) ->
+           {
+             name = name.txt;
+             posStart = Loc.start name.loc;
+             posEnd = Loc.end_ name.loc;
+             exp = value;
+           }
+         | JSXPropSpreading (loc, expr) ->
+           {
+             name = "_spreadProps";
+             posStart = Loc.start loc;
+             posEnd = Loc.end_ loc;
+             exp = expr;
+           })
   in
-  args |> processProps ~acc:[]
+  {compName; props; childrenStart}
diff --git a/analysis/src/SemanticTokens.ml b/analysis/src/SemanticTokens.ml
index cbd435a5c4..10219f1b54 100644
--- a/analysis/src/SemanticTokens.ml
+++ b/analysis/src/SemanticTokens.ml
@@ -120,9 +120,7 @@ let emitLongident ?(backwards = false) ?(jsx = false)
   let rec flatten acc lid =
     match lid with
     | Longident.Lident txt -> txt :: acc
-    | Ldot (lid, txt) ->
-      let acc = if jsx && txt = "createElement" then acc else txt :: acc in
-      flatten acc lid
+    | Ldot (lid, txt) -> flatten (txt :: acc) lid
     | _ -> acc
   in
   let rec loop pos segments =
@@ -247,8 +245,8 @@ let command ~debug ~emitter ~path =
                ~posEnd:(Some (Loc.end_ loc))
                ~lid ~debug;
       Ast_iterator.default_iterator.expr iterator e
-    | Pexp_apply {funct = {pexp_desc = Pexp_ident lident; pexp_loc}; args}
-      when Res_parsetree_viewer.is_jsx_expression e ->
+    | Pexp_jsx_element (Jsx_unary_element {jsx_unary_element_tag_name = lident})
+      ->
       (*
          Angled brackets:
           - These are handled in the grammar:  <>  </>  </  />
@@ -258,46 +256,56 @@ let command ~debug ~emitter ~path =
           - handled like other Longitent.t, except lowercase id is marked Token.JsxLowercase
       *)
       emitter (* --> <div... *)
-      |> emitJsxTag ~debug ~name:"<"
-           ~pos:
-             (let pos = Loc.start e.pexp_loc in
-              (fst pos, snd pos - 1 (* the AST skips the loc of < somehow *)));
-      emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:pexp_loc;
-
-      let posOfGreatherthanAfterProps =
-        let rec loop = function
-          | (Asttypes.Labelled {txt = "children"}, {Parsetree.pexp_loc}) :: _ ->
-            Loc.start pexp_loc
-          | _ :: args -> loop args
-          | [] -> (* should not happen *) (-1, -1)
-        in
-
-        loop args
-      in
-      let posOfFinalGreatherthan =
-        let pos = Loc.end_ e.pexp_loc in
-        (fst pos, snd pos - 1)
-      in
-      let selfClosing =
-        fst posOfGreatherthanAfterProps == fst posOfFinalGreatherthan
-        && snd posOfGreatherthanAfterProps + 1 == snd posOfFinalGreatherthan
-        (* there's an off-by one somehow in the AST *)
-      in
-      (if not selfClosing then
-         let lineStart, colStart = Loc.start pexp_loc in
-         let lineEnd, colEnd = Loc.end_ pexp_loc in
-         let length = if lineStart = lineEnd then colEnd - colStart else 0 in
-         let lineEndWhole, colEndWhole = Loc.end_ e.pexp_loc in
-         if length > 0 && colEndWhole > length then (
-           emitter
-           |> emitJsxClose ~debug ~lid:lident.txt
-                ~pos:(lineEndWhole, colEndWhole - 1);
-           emitter (* <foo ...props > <-- *)
-           |> emitJsxTag ~debug ~name:">" ~pos:posOfGreatherthanAfterProps;
-           emitter (* <foo> ... </foo> <-- *)
-           |> emitJsxTag ~debug ~name:">" ~pos:posOfFinalGreatherthan));
-
-      args |> List.iter (fun (_lbl, arg) -> iterator.expr iterator arg)
+      |> emitJsxTag ~debug ~name:"<" ~pos:(Loc.start e.pexp_loc);
+      emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:lident.loc;
+      let closing_line, closing_column = Loc.end_ e.pexp_loc in
+      emitter (* <foo ...props /> <-- *)
+      |> emitJsxTag ~debug ~name:"/>" ~pos:(closing_line, closing_column - 2)
+      (* minus two for /> *)
+    | Pexp_jsx_element
+        (Jsx_container_element
+           {
+             jsx_container_element_tag_name_start = lident;
+             jsx_container_element_opening_tag_end = posOfGreatherthanAfterProps;
+             jsx_container_element_children = children;
+             jsx_container_element_closing_tag = closing_tag_opt;
+           }) ->
+      (* opening tag *)
+      emitter (* --> <div... *)
+      |> emitJsxTag ~debug ~name:"<" ~pos:(Loc.start e.pexp_loc);
+      emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:lident.loc;
+      emitter (* <foo ...props > <-- *)
+      |> emitJsxTag ~debug ~name:">"
+           ~pos:(Pos.ofLexing posOfGreatherthanAfterProps);
+
+      (* children *)
+      (match children with
+      | Parsetree.JSXChildrenSpreading child -> iterator.expr iterator child
+      | Parsetree.JSXChildrenItems children ->
+        List.iter (iterator.expr iterator) children);
+
+      (* closing tag *)
+      closing_tag_opt
+      |> Option.iter
+           (fun
+             {
+               (* </ *)
+               Parsetree.jsx_closing_container_tag_start = closing_less_than;
+               (* name *)
+               jsx_closing_container_tag_name = tag_name_end;
+               (* > *)
+               jsx_closing_container_tag_end = final_greather_than;
+             }
+           ->
+             emitter
+             |> emitJsxTag ~debug ~name:"</"
+                  ~pos:(Pos.ofLexing closing_less_than);
+             emitter
+             |> emitJsxClose ~debug ~lid:lident.txt
+                  ~pos:(Loc.start tag_name_end.loc);
+             emitter (* <foo> ... </foo> <-- *)
+             |> emitJsxTag ~debug ~name:">"
+                  ~pos:(Pos.ofLexing final_greather_than))
     | Pexp_apply
         {
           funct =
diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml
index 0a015070a7..29e23e6031 100644
--- a/analysis/src/Utils.ml
+++ b/analysis/src/Utils.ml
@@ -112,6 +112,7 @@ let identifyPexp pexp =
   | Pexp_extension _ -> "Pexp_extension"
   | Pexp_open _ -> "Pexp_open"
   | Pexp_await _ -> "Pexp_await"
+  | Pexp_jsx_element _ -> "Pexp_jsx_element"
 
 let identifyPpat pat =
   match pat with
diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml
index c11ee3f207..72799db97b 100644
--- a/compiler/frontend/bs_ast_mapper.ml
+++ b/compiler/frontend/bs_ast_mapper.ml
@@ -292,6 +292,20 @@ module M = struct
 end
 
 module E = struct
+  let map_jsx_children sub = function
+    | JSXChildrenSpreading e -> JSXChildrenSpreading (sub.expr sub e)
+    | JSXChildrenItems xs -> JSXChildrenItems (List.map (sub.expr sub) xs)
+
+  let map_jsx_prop sub = function
+    | JSXPropPunning (optional, name) ->
+      JSXPropPunning (optional, map_loc sub name)
+    | JSXPropValue (name, optional, value) ->
+      JSXPropValue (map_loc sub name, optional, sub.expr sub value)
+    | JSXPropSpreading (loc, e) ->
+      JSXPropSpreading (sub.location sub loc, sub.expr sub e)
+
+  let map_jsx_props sub = List.map (map_jsx_prop sub)
+
   (* Value expressions for the core language *)
 
   let map sub {pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} =
@@ -367,6 +381,31 @@ module E = struct
       open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e)
     | Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x)
     | Pexp_await e -> await ~loc ~attrs (sub.expr sub e)
+    | Pexp_jsx_element
+        (Jsx_fragment
+           {
+             jsx_fragment_opening = o;
+             jsx_fragment_children = children;
+             jsx_fragment_closing = c;
+           }) ->
+      jsx_fragment o (map_jsx_children sub children) c
+    | Pexp_jsx_element
+        (Jsx_unary_element
+           {jsx_unary_element_tag_name = name; jsx_unary_element_props = props})
+      ->
+      jsx_unary_element ~loc ~attrs name (map_jsx_props sub props)
+    | Pexp_jsx_element
+        (Jsx_container_element
+           {
+             jsx_container_element_tag_name_start = name;
+             jsx_container_element_opening_tag_end = ote;
+             jsx_container_element_props = props;
+             jsx_container_element_children = children;
+             jsx_container_element_closing_tag = closing_tag;
+           }) ->
+      jsx_container_element ~loc ~attrs name (map_jsx_props sub props) ote
+        (map_jsx_children sub children)
+        closing_tag
 end
 
 module P = struct
diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml
index 3863a35c3d..d4de7ff0e9 100644
--- a/compiler/ml/ast_helper.ml
+++ b/compiler/ml/ast_helper.ml
@@ -181,7 +181,59 @@ module Exp = struct
   let open_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_open (a, b, c))
   let extension ?loc ?attrs a = mk ?loc ?attrs (Pexp_extension a)
   let await ?loc ?attrs a = mk ?loc ?attrs (Pexp_await a)
+  let jsx_fragment ?loc ?attrs a b c =
+    mk ?loc ?attrs
+      (Pexp_jsx_element
+         (Jsx_fragment
+            {
+              jsx_fragment_opening = a;
+              jsx_fragment_children = b;
+              jsx_fragment_closing = c;
+            }))
+  let jsx_unary_element ?loc ?attrs a b =
+    mk ?loc ?attrs
+      (Pexp_jsx_element
+         (Jsx_unary_element
+            {jsx_unary_element_tag_name = a; jsx_unary_element_props = b}))
+
+  let jsx_container_element ?loc ?attrs a b c d e =
+    mk ?loc ?attrs
+      (Pexp_jsx_element
+         (Jsx_container_element
+            {
+              jsx_container_element_tag_name_start = a;
+              jsx_container_element_props = b;
+              jsx_container_element_opening_tag_end = c;
+              jsx_container_element_children = d;
+              jsx_container_element_closing_tag = e;
+            }))
+
   let case lhs ?guard rhs = {pc_lhs = lhs; pc_guard = guard; pc_rhs = rhs}
+
+  let make_list_expression loc seq ext_opt =
+    let rec handle_seq = function
+      | [] -> (
+        match ext_opt with
+        | Some ext -> ext
+        | None ->
+          let loc = {loc with Location.loc_ghost = true} in
+          let nil = Location.mkloc (Longident.Lident "[]") loc in
+          construct ~loc nil None)
+      | e1 :: el ->
+        let exp_el = handle_seq el in
+        let loc =
+          Location.
+            {
+              loc_start = e1.Parsetree.pexp_loc.Location.loc_start;
+              loc_end = exp_el.pexp_loc.loc_end;
+              loc_ghost = false;
+            }
+        in
+        let arg = tuple ~loc [e1; exp_el] in
+        construct ~loc (Location.mkloc (Longident.Lident "::") loc) (Some arg)
+    in
+    let expr = handle_seq seq in
+    {expr with pexp_loc = loc}
 end
 
 module Mty = struct
diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli
index 1cc3f9679d..10677c31b2 100644
--- a/compiler/ml/ast_helper.mli
+++ b/compiler/ml/ast_helper.mli
@@ -208,9 +208,34 @@ module Exp : sig
   val open_ :
     ?loc:loc -> ?attrs:attrs -> override_flag -> lid -> expression -> expression
   val extension : ?loc:loc -> ?attrs:attrs -> extension -> expression
+  val jsx_fragment :
+    ?loc:loc ->
+    ?attrs:attrs ->
+    Lexing.position ->
+    Parsetree.jsx_children ->
+    Lexing.position ->
+    expression
+  val jsx_unary_element :
+    ?loc:loc ->
+    ?attrs:attrs ->
+    Longident.t Location.loc ->
+    Parsetree.jsx_props ->
+    expression
+  val jsx_container_element :
+    ?loc:loc ->
+    ?attrs:attrs ->
+    Longident.t Location.loc ->
+    Parsetree.jsx_props ->
+    Lexing.position ->
+    Parsetree.jsx_children ->
+    Parsetree.jsx_closing_container_tag option ->
+    expression
 
   val case : pattern -> ?guard:expression -> expression -> case
   val await : ?loc:loc -> ?attrs:attrs -> expression -> expression
+
+  val make_list_expression :
+    Location.t -> expression list -> expression option -> expression
 end
 
 (** Value declarations *)
diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml
index b6f4e101b9..9790cfc839 100644
--- a/compiler/ml/ast_iterator.ml
+++ b/compiler/ml/ast_iterator.ml
@@ -267,6 +267,19 @@ module M = struct
 end
 
 module E = struct
+  let iter_jsx_children sub = function
+    | JSXChildrenSpreading e -> sub.expr sub e
+    | JSXChildrenItems xs -> List.iter (sub.expr sub) xs
+
+  let iter_jsx_prop sub = function
+    | JSXPropPunning (_, name) -> iter_loc sub name
+    | JSXPropValue (name, _, value) ->
+      iter_loc sub name;
+      sub.expr sub value
+    | JSXPropSpreading (_, e) -> sub.expr sub e
+
+  let iter_jsx_props sub = List.iter (iter_jsx_prop sub)
+
   (* Value expressions for the core language *)
 
   let iter sub {pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} =
@@ -345,6 +358,24 @@ module E = struct
       sub.expr sub e
     | Pexp_extension x -> sub.extension sub x
     | Pexp_await e -> sub.expr sub e
+    | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
+      iter_jsx_children sub children
+    | Pexp_jsx_element
+        (Jsx_unary_element
+           {jsx_unary_element_tag_name = name; jsx_unary_element_props = props})
+      ->
+      iter_loc sub name;
+      iter_jsx_props sub props
+    | Pexp_jsx_element
+        (Jsx_container_element
+           {
+             jsx_container_element_tag_name_start = name;
+             jsx_container_element_props = props;
+             jsx_container_element_children = children;
+           }) ->
+      iter_loc sub name;
+      iter_jsx_props sub props;
+      iter_jsx_children sub children
 end
 
 module P = struct
diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml
index a4a70c18ee..992d1a9816 100644
--- a/compiler/ml/ast_mapper.ml
+++ b/compiler/ml/ast_mapper.ml
@@ -263,6 +263,20 @@ module M = struct
 end
 
 module E = struct
+  let map_jsx_children sub = function
+    | JSXChildrenSpreading e -> JSXChildrenSpreading (sub.expr sub e)
+    | JSXChildrenItems xs -> JSXChildrenItems (List.map (sub.expr sub) xs)
+
+  let map_jsx_prop sub = function
+    | JSXPropPunning (optional, name) ->
+      JSXPropPunning (optional, map_loc sub name)
+    | JSXPropValue (name, optional, value) ->
+      JSXPropValue (map_loc sub name, optional, sub.expr sub value)
+    | JSXPropSpreading (loc, e) ->
+      JSXPropSpreading (sub.location sub loc, sub.expr sub e)
+
+  let map_jsx_props sub = List.map (map_jsx_prop sub)
+
   (* Value expressions for the core language *)
 
   let map sub {pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} =
@@ -330,6 +344,32 @@ module E = struct
       open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e)
     | Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x)
     | Pexp_await e -> await ~loc ~attrs (sub.expr sub e)
+    | Pexp_jsx_element
+        (Jsx_fragment
+           {
+             jsx_fragment_opening = o;
+             jsx_fragment_children = children;
+             jsx_fragment_closing = c;
+           }) ->
+      jsx_fragment ~loc ~attrs o (map_jsx_children sub children) c
+    | Pexp_jsx_element
+        (Jsx_unary_element
+           {jsx_unary_element_tag_name = name; jsx_unary_element_props = props})
+      ->
+      jsx_unary_element ~loc ~attrs (map_loc sub name) (map_jsx_props sub props)
+    | Pexp_jsx_element
+        (Jsx_container_element
+           {
+             jsx_container_element_tag_name_start = name;
+             jsx_container_element_opening_tag_end = ote;
+             jsx_container_element_props = props;
+             jsx_container_element_children = children;
+             jsx_container_element_closing_tag = closing_tag;
+           }) ->
+      jsx_container_element ~loc ~attrs (map_loc sub name)
+        (map_jsx_props sub props) ote
+        (map_jsx_children sub children)
+        closing_tag
 end
 
 module P = struct
diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml
index 0141197bea..2b09726757 100644
--- a/compiler/ml/ast_mapper_from0.ml
+++ b/compiler/ml/ast_mapper_from0.ml
@@ -309,11 +309,72 @@ module E = struct
         | _ -> true)
       attrs
 
-  let map sub ({pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} as e)
-      =
+  let map_jsx_children sub (e : expression) : Pt.jsx_children =
+    let rec visit (e : expression) : Pt.expression list =
+      match e.pexp_desc with
+      | Pexp_construct
+          ({txt = Longident.Lident "::"}, Some {pexp_desc = Pexp_tuple [e1; e2]})
+        ->
+        sub.expr sub e1 :: visit e2
+      | Pexp_construct ({txt = Longident.Lident "[]"}, ext_opt) -> (
+        match ext_opt with
+        | None -> []
+        | Some e -> visit e)
+      | _ -> [sub.expr sub e]
+    in
+    match e.pexp_desc with
+    | Pexp_construct ({txt = Longident.Lident "[]" | Longident.Lident "::"}, _)
+      ->
+      JSXChildrenItems (visit e)
+    | _ -> JSXChildrenSpreading (sub.expr sub e)
+
+  let try_map_jsx_prop (sub : mapper) (lbl : Asttypes.Noloc.arg_label)
+      (e : expression) : Parsetree.jsx_prop option =
+    match (lbl, e) with
+    | Asttypes.Noloc.Labelled "_spreadProps", expr ->
+      Some (Parsetree.JSXPropSpreading (Location.none, sub.expr sub expr))
+    | ( Asttypes.Noloc.Labelled name,
+        {pexp_desc = Pexp_ident {txt = Longident.Lident v}; pexp_loc = name_loc}
+      )
+      when name = v ->
+      Some (Parsetree.JSXPropPunning (false, {txt = name; loc = name_loc}))
+    | ( Asttypes.Noloc.Optional name,
+        {pexp_desc = Pexp_ident {txt = Longident.Lident v}; pexp_loc = name_loc}
+      )
+      when name = v ->
+      Some (Parsetree.JSXPropPunning (true, {txt = name; loc = name_loc}))
+    | Asttypes.Noloc.Labelled name, exp ->
+      Some
+        (Parsetree.JSXPropValue
+           ({txt = name; loc = Location.none}, false, sub.expr sub exp))
+    | Asttypes.Noloc.Optional name, exp ->
+      Some
+        (Parsetree.JSXPropValue
+           ({txt = name; loc = Location.none}, true, sub.expr sub exp))
+    | _ -> None
+
+  let extract_props_and_children (sub : mapper) items =
+    let rec visit props items =
+      match items with
+      | [] | [_] -> (List.rev props, None)
+      | [(Asttypes.Noloc.Labelled "children", children_expr); _] ->
+        (List.rev props, Some (map_jsx_children sub children_expr))
+      | (lbl, e) :: rest -> (
+        match try_map_jsx_prop sub lbl e with
+        | Some prop -> visit (prop :: props) rest
+        | None -> visit props rest)
+    in
+    let props, children = visit [] items in
+    (props, children)
+
+  let map sub e =
+    let {pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} = e in
     let open Exp in
     let loc = sub.location sub loc in
     let attrs = sub.attributes sub attrs in
+    let has_jsx_attribute () =
+      attrs |> List.exists (fun ({txt}, _) -> txt = "JSX")
+    in
     match desc with
     | _ when has_await_attribute attrs ->
       let attrs = remove_await_attribute e.pexp_attributes in
@@ -330,6 +391,15 @@ module E = struct
         (map_opt (sub.expr sub) def)
         (sub.pat sub p) (sub.expr sub e)
     | Pexp_function _ -> assert false
+    | Pexp_apply ({pexp_desc = Pexp_ident tag_name}, args)
+      when has_jsx_attribute () -> (
+      let attrs = attrs |> List.filter (fun ({txt}, _) -> txt <> "JSX") in
+      let props, children = extract_props_and_children sub args in
+      match children with
+      | None -> jsx_unary_element ~loc ~attrs tag_name props
+      | Some children ->
+        jsx_container_element ~loc ~attrs tag_name props Lexing.dummy_pos
+          children None)
     | Pexp_apply (e, l) ->
       let e =
         match (e.pexp_desc, l) with
@@ -377,6 +447,12 @@ module E = struct
       match_ ~loc ~attrs (sub.expr sub e) (sub.cases sub pel)
     | Pexp_try (e, pel) -> try_ ~loc ~attrs (sub.expr sub e) (sub.cases sub pel)
     | Pexp_tuple el -> tuple ~loc ~attrs (List.map (sub.expr sub) el)
+    (* <></> *)
+    | Pexp_construct ({txt = Longident.Lident "[]" | Longident.Lident "::"}, _)
+      when has_jsx_attribute () ->
+      let attrs = attrs |> List.filter (fun ({txt}, _) -> txt <> "JSX") in
+      jsx_fragment ~loc ~attrs loc.loc_start (map_jsx_children sub e)
+        loc.loc_end
     | Pexp_construct (lid, arg) -> (
       let lid1 = map_loc sub lid in
       let arg1 = map_opt (sub.expr sub) arg in
diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml
index 4b09f30b26..0f5494bb23 100644
--- a/compiler/ml/ast_mapper_to0.ml
+++ b/compiler/ml/ast_mapper_to0.ml
@@ -281,6 +281,66 @@ module M = struct
 end
 
 module E = struct
+  let jsx_attr sub =
+    sub.attribute sub (Location.mknoloc "JSX", Parsetree.PStr [])
+
+  let offset_position (pos : Lexing.position) (offset : int) : Lexing.position =
+    if offset <= 0 then pos
+    else
+      let open Lexing in
+      let rec aux pos offset =
+        if offset <= 0 then pos
+        else if offset <= pos.pos_cnum - pos.pos_bol then
+          (* We're on the same line *)
+          {pos with pos_cnum = pos.pos_cnum - offset}
+        else
+          (* Move to previous line and continue *)
+          let remaining = offset - (pos.pos_cnum - pos.pos_bol) in
+          aux
+            {
+              pos with
+              pos_lnum = pos.pos_lnum - 1;
+              pos_cnum = pos.pos_bol;
+              pos_bol = max 0 (pos.pos_bol - remaining);
+            }
+            remaining
+      in
+      aux pos offset
+
+  let jsx_unit_expr =
+    Ast_helper0.Exp.construct ~loc:!Ast_helper0.default_loc
+      {txt = Lident "()"; loc = !Ast_helper0.default_loc}
+      None
+
+  let map_jsx_props sub props =
+    props
+    |> List.map (function
+         | JSXPropPunning (is_optional, name) ->
+           let ident =
+             Exp.ident ~loc:name.loc
+               {txt = Longident.Lident name.txt; loc = name.loc}
+           in
+           let label =
+             if is_optional then Asttypes.Noloc.Optional name.txt
+             else Asttypes.Noloc.Labelled name.txt
+           in
+           (label, ident)
+         | JSXPropValue (name, is_optional, value) ->
+           let label =
+             if is_optional then Asttypes.Noloc.Optional name.txt
+             else Asttypes.Noloc.Labelled name.txt
+           in
+           (label, sub.expr sub value)
+         | JSXPropSpreading (_, value) ->
+           (Asttypes.Noloc.Labelled "_spreadProps", sub.expr sub value))
+
+  let map_jsx_children sub loc children =
+    match children with
+    | JSXChildrenSpreading e -> sub.expr sub e
+    | JSXChildrenItems xs ->
+      let list_expr = Ast_helper.Exp.make_list_expression loc xs None in
+      sub.expr sub list_expr
+
   (* Value expressions for the core language *)
 
   let map sub {pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} =
@@ -414,6 +474,65 @@ module E = struct
         pexp_attributes =
           (Location.mknoloc "res.await", Pt.PStr []) :: e.pexp_attributes;
       }
+    | Pexp_jsx_element
+        (Jsx_fragment
+           {
+             jsx_fragment_opening = o;
+             jsx_fragment_children = children;
+             jsx_fragment_closing = c;
+           }) ->
+      (*
+         The location of  Pexp_jsx_fragment is from the start of < till the end of />.
+         This is not the case in the old AST. There it is from >...</
+      *)
+      let loc = {loc with loc_start = o; loc_end = c} in
+      let mapped = map_jsx_children sub loc children in
+      {mapped with pexp_attributes = jsx_attr sub :: attrs}
+    | Pexp_jsx_element
+        (Jsx_unary_element
+           {
+             jsx_unary_element_tag_name = tag_name;
+             jsx_unary_element_props = props;
+           }) ->
+      let tag_ident = map_loc sub tag_name in
+      let props = map_jsx_props sub props in
+      let children_expr =
+        let loc =
+          {
+            loc_ghost = true;
+            loc_start = offset_position loc.loc_end 2;
+            loc_end = offset_position loc.loc_end 1;
+          }
+        in
+        Ast_helper0.Exp.construct ~loc {txt = Lident "[]"; loc} None
+      in
+      let unit_expr =
+        Ast_helper0.Exp.construct ~loc:!Ast_helper0.default_loc
+          {txt = Lident "()"; loc = !Ast_helper0.default_loc}
+          None
+      in
+      apply ~loc ~attrs:(jsx_attr sub :: attrs) (ident tag_ident)
+        (props
+        @ [
+            (Asttypes.Noloc.Labelled "children", children_expr);
+            (Asttypes.Noloc.Nolabel, unit_expr);
+          ])
+    | Pexp_jsx_element
+        (Jsx_container_element
+           {
+             jsx_container_element_tag_name_start = tag_name;
+             jsx_container_element_props = props;
+             jsx_container_element_children = children;
+           }) ->
+      let tag_ident = map_loc sub tag_name in
+      let props = map_jsx_props sub props in
+      let children_expr = map_jsx_children sub loc children in
+      apply ~loc ~attrs:(jsx_attr sub :: attrs) (ident tag_ident)
+        (props
+        @ [
+            (Asttypes.Noloc.Labelled "children", children_expr);
+            (Asttypes.Noloc.Nolabel, jsx_unit_expr);
+          ])
 end
 
 module P = struct
diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml
index 0854e2c5d3..ea3c71b947 100644
--- a/compiler/ml/depend.ml
+++ b/compiler/ml/depend.ml
@@ -290,6 +290,35 @@ let rec add_expr bv exp =
     | _ -> handle_extension e)
   | Pexp_extension e -> handle_extension e
   | Pexp_await e -> add_expr bv e
+  | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
+    add_jsx_children bv children
+  | Pexp_jsx_element
+      (Jsx_unary_element
+         {jsx_unary_element_tag_name = name; jsx_unary_element_props = props})
+    ->
+    add bv name;
+    and_jsx_props bv props
+  | Pexp_jsx_element
+      (Jsx_container_element
+         {
+           jsx_container_element_tag_name_start = name;
+           jsx_container_element_props = props;
+           jsx_container_element_children = children;
+         }) ->
+    add bv name;
+    and_jsx_props bv props;
+    add_jsx_children bv children
+
+and add_jsx_children bv = function
+  | JSXChildrenSpreading e -> add_expr bv e
+  | JSXChildrenItems xs -> List.iter (add_expr bv) xs
+
+and add_jsx_prop bv = function
+  | JSXPropPunning (_, _) -> ()
+  | JSXPropValue (_, _, e) -> add_expr bv e
+  | JSXPropSpreading (_, e) -> add_expr bv e
+
+and and_jsx_props bv = List.iter (add_jsx_prop bv)
 
 and add_cases bv cases = List.iter (add_case bv) cases
 
diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml
index c8a8c7d60a..5c47210630 100644
--- a/compiler/ml/parsetree.ml
+++ b/compiler/ml/parsetree.ml
@@ -316,6 +316,68 @@ and expression_desc =
   (* [%id] *)
   (* . *)
   | Pexp_await of expression
+  | Pexp_jsx_element of jsx_element
+
+and jsx_element =
+  | Jsx_fragment of jsx_fragment
+  | Jsx_unary_element of jsx_unary_element
+  | Jsx_container_element of jsx_container_element
+
+and jsx_fragment = {
+  (* > *) jsx_fragment_opening: Lexing.position;
+  (* children *) jsx_fragment_children: jsx_children;
+  (* </ *) jsx_fragment_closing: Lexing.position;
+}
+
+and jsx_unary_element = {
+  jsx_unary_element_tag_name: Longident.t loc;
+  jsx_unary_element_props: jsx_props;
+}
+
+and jsx_container_element = {
+  (* jsx_container_element_opening_tag_start: Lexing.position; *)
+  jsx_container_element_tag_name_start: Longident.t loc;
+  (* > *)
+  jsx_container_element_opening_tag_end: Lexing.position;
+  jsx_container_element_props: jsx_props;
+  jsx_container_element_children: jsx_children;
+  jsx_container_element_closing_tag: jsx_closing_container_tag option;
+}
+
+and jsx_prop =
+  (*
+   *   |  lident
+   *   | ?lident
+   *)
+  | JSXPropPunning of (* optional *) bool * (* name *) string loc
+  (*
+   *   |  lident =  jsx_expr
+   *   |  lident = ?jsx_expr
+   *)
+  | JSXPropValue of
+      (* name *) string loc * (* optional *) bool * (* value *) expression
+  (*
+   *   |  {...jsx_expr}
+   *)
+  | JSXPropSpreading of
+      (* entire {...expr} location *)
+      Location.t
+      * expression
+
+and jsx_children =
+  | JSXChildrenSpreading of expression
+  | JSXChildrenItems of expression list
+
+and jsx_props = jsx_prop list
+
+and jsx_closing_container_tag = {
+  (* </ *)
+  jsx_closing_container_tag_start: Lexing.position;
+  (* name *)
+  jsx_closing_container_tag_name: Longident.t loc;
+  (* > *)
+  jsx_closing_container_tag_end: Lexing.position;
+}
 
 and case = {
   (* (P -> E) or (P when E0 -> E) *)
diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml
index 73f31671d6..9a1c823cd8 100644
--- a/compiler/ml/pprintast.ml
+++ b/compiler/ml/pprintast.ml
@@ -795,8 +795,54 @@ and simple_expr ctxt f x =
       let expression = expression ctxt in
       pp f fmt (pattern ctxt) s expression e1 direction_flag df expression e2
         expression e3
+    | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
+      pp f "<>%a</>" (list (simple_expr ctxt)) (collect_jsx_children children)
+    | Pexp_jsx_element
+        (Jsx_unary_element
+           {
+             jsx_unary_element_tag_name = tag_name;
+             jsx_unary_element_props = props;
+           }) -> (
+      let name = Longident.flatten tag_name.txt |> String.concat "." in
+      match props with
+      | [] -> pp f "<%s />" name
+      | _ -> pp f "<%s %a />" name (print_jsx_props ctxt) props)
+    | Pexp_jsx_element
+        (Jsx_container_element
+           {
+             jsx_container_element_tag_name_start = tag_name;
+             jsx_container_element_props = props;
+             jsx_container_element_children = children;
+           }) -> (
+      let name = Longident.flatten tag_name.txt |> String.concat "." in
+      match props with
+      | [] ->
+        pp f "<%s>%a</%s>" name
+          (list (simple_expr ctxt))
+          (collect_jsx_children children)
+          name
+      | _ ->
+        pp f "<%s %a>%a</%s>" name (print_jsx_props ctxt) props
+          (list (simple_expr ctxt))
+          (collect_jsx_children children)
+          name)
     | _ -> paren true (expression ctxt) f x
 
+and collect_jsx_children = function
+  | JSXChildrenSpreading e -> [e]
+  | JSXChildrenItems xs -> xs
+
+and print_jsx_prop ctxt f = function
+  | JSXPropPunning (is_optional, name) ->
+    pp f "%s" (if is_optional then "?" ^ name.txt else name.txt)
+  | JSXPropValue (name, is_optional, value) ->
+    pp f "%s=%s%a" name.txt
+      (if is_optional then "?" else "")
+      (simple_expr ctxt) value
+  | JSXPropSpreading (_, expr) -> pp f "{...%a}" (simple_expr ctxt) expr
+
+and print_jsx_props ctxt f = list ~sep:" " (print_jsx_prop ctxt) f
+
 and attributes ctxt f l = List.iter (attribute ctxt f) l
 
 and item_attributes ctxt f l = List.iter (item_attribute ctxt f) l
diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml
index 53e76a608c..380939b15b 100644
--- a/compiler/ml/printast.ml
+++ b/compiler/ml/printast.ml
@@ -348,6 +348,48 @@ and expression i ppf x =
   | Pexp_await e ->
     line i ppf "Pexp_await\n";
     expression i ppf e
+  | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
+    line i ppf "Pexp_jsx_fragment";
+    jsx_children i ppf children
+  | Pexp_jsx_element
+      (Jsx_unary_element
+         {jsx_unary_element_tag_name = name; jsx_unary_element_props = props})
+    ->
+    line i ppf "Pexp_jsx_unary_element %a\n" fmt_longident_loc name;
+    jsx_props i ppf props
+  | Pexp_jsx_element
+      (Jsx_container_element
+         {
+           jsx_container_element_tag_name_start = name;
+           jsx_container_element_props = props;
+           jsx_container_element_opening_tag_end = gt;
+           jsx_container_element_children = children;
+         }) ->
+    line i ppf "Pexp_jsx_container_element %a\n" fmt_longident_loc name;
+    jsx_props i ppf props;
+    if !Clflags.dump_location then line i ppf "> %a\n" (fmt_position false) gt;
+    jsx_children i ppf children
+
+and jsx_children i ppf children =
+  line i ppf "jsx_children =\n";
+  match children with
+  | JSXChildrenSpreading e -> expression (i + 1) ppf e
+  | JSXChildrenItems xs -> list (i + 1) expression ppf xs
+
+and jsx_prop i ppf = function
+  | JSXPropPunning (opt, name) ->
+    line i ppf "%s%s" (if opt then "?" else "") name.txt
+  | JSXPropValue (name, opt, expr) ->
+    line i ppf "%s=%s" name.txt (if opt then "?" else "");
+    expression i ppf expr
+  | JSXPropSpreading (loc, e) ->
+    line i ppf "{... %a\n" fmt_location loc;
+    expression (i + 1) ppf e;
+    line i ppf "}\n"
+
+and jsx_props i ppf xs =
+  line i ppf "jsx_props =\n";
+  list (i + 1) jsx_prop ppf xs
 
 and value_description i ppf x =
   line i ppf "value_description %a %a\n" fmt_string_loc x.pval_name fmt_location
diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml
index c3f2907aab..d1e593607f 100644
--- a/compiler/ml/typecore.ml
+++ b/compiler/ml/typecore.ml
@@ -179,6 +179,8 @@ let iter_expression f e =
       module_expr me
     | Pexp_pack me -> module_expr me
     | Pexp_await _ -> assert false (* should be handled earlier *)
+    | Pexp_jsx_element _ ->
+      failwith "Pexp_jsx_element should be transformed at this point."
   and case {pc_lhs = _; pc_guard; pc_rhs} =
     may expr pc_guard;
     expr pc_rhs
@@ -3197,6 +3199,8 @@ and type_expect_ ?type_clash_context ?in_function ?(recarg = Rejected) env sexp
   | Pexp_extension ext ->
     raise (Error_forward (Builtin_attributes.error_of_extension ext))
   | Pexp_await _ -> (* should be handled earlier *) assert false
+  | Pexp_jsx_element _ ->
+    failwith "Pexp_jsx_element is expected to be transformed at this point"
 
 and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l
     caselist =
diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml
index a3bb5fda7f..febc245f21 100644
--- a/compiler/syntax/src/jsx_v4.ml
+++ b/compiler/syntax/src/jsx_v4.ml
@@ -10,8 +10,6 @@ let module_access_name config value =
 
 let nolabel = Nolabel
 
-let labelled str = Labelled {txt = str; loc = Location.none}
-
 let is_optional str =
   match str with
   | Optional _ -> true
@@ -34,9 +32,6 @@ let get_label str =
 let constant_string ~loc str =
   Ast_helper.Exp.constant ~loc (Pconst_string (str, None))
 
-(* {} empty record *)
-let empty_record ~loc = Exp.record ~loc [] None
-
 let unit_expr ~loc = Exp.construct ~loc (Location.mkloc (Lident "()") loc) None
 
 let safe_type_from_value value_str =
@@ -51,71 +46,6 @@ let ref_type loc =
     {loc; txt = Ldot (Ldot (Lident "Js", "Nullable"), "t")}
     [ref_type_var loc]
 
-type 'a children = ListLiteral of 'a | Exact of 'a
-
-(* if children is a list, convert it to an array while mapping each element. If not, just map over it, as usual *)
-let transform_children_if_list_upper ~mapper the_list =
-  let rec transformChildren_ the_list accum =
-    (* not in the sense of converting a list to an array; convert the AST
-       reprensentation of a list to the AST reprensentation of an array *)
-    match the_list with
-    | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} -> (
-      match accum with
-      | [single_element] -> Exact single_element
-      | accum -> ListLiteral (Exp.array (List.rev accum)))
-    | {
-     pexp_desc =
-       Pexp_construct
-         ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]});
-    } ->
-      transformChildren_ acc (mapper.expr mapper v :: accum)
-    | not_a_list -> Exact (mapper.expr mapper not_a_list)
-  in
-  transformChildren_ the_list []
-
-let transform_children_if_list ~mapper the_list =
-  let rec transformChildren_ the_list accum =
-    (* not in the sense of converting a list to an array; convert the AST
-       reprensentation of a list to the AST reprensentation of an array *)
-    match the_list with
-    | {pexp_desc = Pexp_construct ({txt = Lident "[]"}, None)} ->
-      Exp.array (List.rev accum)
-    | {
-     pexp_desc =
-       Pexp_construct
-         ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple [v; acc]});
-    } ->
-      transformChildren_ acc (mapper.expr mapper v :: accum)
-    | not_a_list -> mapper.expr mapper not_a_list
-  in
-  transformChildren_ the_list []
-
-let extract_children ~loc props_and_children =
-  let rec allButLast_ lst acc =
-    match lst with
-    | [] -> []
-    | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})] ->
-      acc
-    | (Nolabel, {pexp_loc}) :: _rest ->
-      Jsx_common.raise_error ~loc:pexp_loc
-        "JSX: found non-labelled argument before the last position"
-    | arg :: rest -> allButLast_ rest (arg :: acc)
-  in
-  let all_but_last lst = allButLast_ lst [] |> List.rev in
-  match
-    List.partition
-      (fun (label, _) -> label = labelled "children")
-      props_and_children
-  with
-  | [], props ->
-    (* no children provided? Place a placeholder list *)
-    ( Exp.construct {loc = Location.none; txt = Lident "[]"} None,
-      all_but_last props )
-  | [(_, children_expr)], props -> (children_expr, all_but_last props)
-  | _ ->
-    Jsx_common.raise_error ~loc
-      "JSX: somehow there's more than one `children` label"
-
 let merlin_focus = ({loc = Location.none; txt = "merlin.focus"}, PStr [])
 
 (* Helper method to filter out any attribute that isn't [@react.component] *)
@@ -177,76 +107,6 @@ let make_module_name file_name nested_modules fn_name =
   let full_module_name = String.concat "$" full_module_name in
   full_module_name
 
-(*
-  AST node builders
-  These functions help us build AST nodes that are needed when transforming a [@react.component] into a
-  constructor and a props external
-  *)
-
-(* make record from props and spread props if exists *)
-let record_from_props ~loc ~remove_key call_arguments =
-  let spread_props_label = "_spreadProps" in
-  let rec remove_last_position_unit_aux props acc =
-    match props with
-    | [] -> acc
-    | [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)}, _)]
-      ->
-      acc
-    | (Nolabel, {pexp_loc}, _) :: _rest ->
-      Jsx_common.raise_error ~loc:pexp_loc
-        "JSX: found non-labelled argument before the last position"
-    | ((Labelled {txt}, {pexp_loc}, _) as prop) :: rest
-    | ((Optional {txt}, {pexp_loc}, _) as prop) :: rest ->
-      if txt = spread_props_label then
-        match acc with
-        | [] -> remove_last_position_unit_aux rest (prop :: acc)
-        | _ ->
-          Jsx_common.raise_error ~loc:pexp_loc
-            "JSX: use {...p} {x: v} not {x: v} {...p} \n\
-            \     multiple spreads {...p} {...p} not allowed."
-      else remove_last_position_unit_aux rest (prop :: acc)
-  in
-  let props, props_to_spread =
-    remove_last_position_unit_aux call_arguments []
-    |> List.rev
-    |> List.partition (fun (label, _, _) ->
-           match label with
-           | Labelled {txt = "_spreadProps"} -> false
-           | _ -> true)
-  in
-  let props =
-    if remove_key then
-      props
-      |> List.filter (fun (arg_label, _, _) -> "key" <> get_label arg_label)
-    else props
-  in
-
-  let process_prop (arg_label, ({pexp_loc} as pexpr), optional) =
-    (* In case filed label is "key" only then change expression to option *)
-    let id = get_label arg_label in
-    ({txt = Lident id; loc = pexp_loc}, pexpr, optional || is_optional arg_label)
-  in
-  let fields = props |> List.map process_prop in
-  let spread_fields =
-    props_to_spread |> List.map (fun (_, expression, _) -> expression)
-  in
-  match (fields, spread_fields) with
-  | [], [spread_props] | [], spread_props :: _ -> spread_props
-  | _, [] ->
-    {
-      pexp_desc = Pexp_record (fields, None);
-      pexp_loc = loc;
-      pexp_attributes = [];
-    }
-  | _, [spread_props]
-  (* take the first spreadProps only *)
-  | _, spread_props :: _ ->
-    {
-      pexp_desc = Pexp_record (fields, Some spread_props);
-      pexp_loc = loc;
-      pexp_attributes = [];
-    }
-
 (* make type params for make fn arguments *)
 (* let make = ({id, name, children}: props<'id, 'name, 'children>) *)
 let make_props_type_params_tvar named_type_list =
@@ -384,168 +244,6 @@ let make_props_record_type_sig ~core_type_of_attr ~external_
       make_type_decls_with_core_type props_name loc core_type
         typ_vars_of_core_type)
 
-let transform_uppercase_call3 ~config module_path mapper jsx_expr_loc
-    call_expr_loc attrs call_arguments =
-  let children, args_with_labels =
-    extract_children ~loc:jsx_expr_loc call_arguments
-  in
-  let args_for_make = args_with_labels in
-  let children_expr = transform_children_if_list_upper ~mapper children in
-  let recursively_transformed_args_for_make =
-    args_for_make
-    |> List.map (fun (label, expression) ->
-           (label, mapper.expr mapper expression, false))
-  in
-  let children_arg = ref None in
-  let args =
-    recursively_transformed_args_for_make
-    @
-    match children_expr with
-    | Exact children -> [(labelled "children", children, false)]
-    | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> []
-    | ListLiteral expression ->
-      (* this is a hack to support react components that introspect into their children *)
-      children_arg := Some expression;
-      [
-        ( labelled "children",
-          Exp.apply
-            (Exp.ident
-               {txt = module_access_name config "array"; loc = Location.none})
-            [(Nolabel, expression)],
-          false );
-      ]
-  in
-
-  let is_cap str = String.capitalize_ascii str = str in
-  let ident ~suffix =
-    match module_path with
-    | Lident _ -> Ldot (module_path, suffix)
-    | Ldot (_modulePath, value) as full_path when is_cap value ->
-      Ldot (full_path, suffix)
-    | module_path -> module_path
-  in
-  let is_empty_record {pexp_desc} =
-    match pexp_desc with
-    | Pexp_record (label_decls, _) when List.length label_decls = 0 -> true
-    | _ -> false
-  in
-
-  (* handle key, ref, children *)
-  (* React.createElement(Component.make, props, ...children) *)
-  let record = record_from_props ~loc:jsx_expr_loc ~remove_key:true args in
-  let props =
-    if is_empty_record record then empty_record ~loc:jsx_expr_loc else record
-  in
-  let key_prop =
-    args
-    |> List.filter_map (fun (arg_label, e, _opt) ->
-           if "key" = get_label arg_label then Some (arg_label, e) else None)
-  in
-  let make_i_d =
-    Exp.ident ~loc:call_expr_loc
-      {txt = ident ~suffix:"make"; loc = call_expr_loc}
-  in
-
-  let jsx_expr, key_and_unit =
-    match (!children_arg, key_prop) with
-    | None, key :: _ ->
-      ( Exp.ident
-          {loc = Location.none; txt = module_access_name config "jsxKeyed"},
-        [key; (nolabel, unit_expr ~loc:Location.none)] )
-    | None, [] ->
-      ( Exp.ident {loc = Location.none; txt = module_access_name config "jsx"},
-        [] )
-    | Some _, key :: _ ->
-      ( Exp.ident
-          {loc = Location.none; txt = module_access_name config "jsxsKeyed"},
-        [key; (nolabel, unit_expr ~loc:Location.none)] )
-    | Some _, [] ->
-      ( Exp.ident {loc = Location.none; txt = module_access_name config "jsxs"},
-        [] )
-  in
-  Exp.apply ~loc:jsx_expr_loc ~attrs jsx_expr
-    ([(nolabel, make_i_d); (nolabel, props)] @ key_and_unit)
-
-let transform_lowercase_call3 ~config mapper jsx_expr_loc call_expr_loc attrs
-    call_arguments id =
-  let component_name_expr = constant_string ~loc:call_expr_loc id in
-  let element_binding =
-    match config.Jsx_common.module_ |> String.lowercase_ascii with
-    | "react" -> Lident "ReactDOM"
-    | _generic -> module_access_name config "Elements"
-  in
-
-  let children, non_children_props =
-    extract_children ~loc:jsx_expr_loc call_arguments
-  in
-  let args_for_make = non_children_props in
-  let children_expr = transform_children_if_list_upper ~mapper children in
-  let recursively_transformed_args_for_make =
-    args_for_make
-    |> List.map (fun (label, expression) ->
-           (label, mapper.expr mapper expression, false))
-  in
-  let children_arg = ref None in
-  let args =
-    recursively_transformed_args_for_make
-    @
-    match children_expr with
-    | Exact children ->
-      [
-        ( labelled "children",
-          Exp.apply
-            (Exp.ident
-               {
-                 txt = Ldot (element_binding, "someElement");
-                 loc = Location.none;
-               })
-            [(Nolabel, children)],
-          true );
-      ]
-    | ListLiteral {pexp_desc = Pexp_array list} when list = [] -> []
-    | ListLiteral expression ->
-      (* this is a hack to support react components that introspect into their children *)
-      children_arg := Some expression;
-      [
-        ( labelled "children",
-          Exp.apply
-            (Exp.ident
-               {txt = module_access_name config "array"; loc = Location.none})
-            [(Nolabel, expression)],
-          false );
-      ]
-  in
-  let is_empty_record {pexp_desc} =
-    match pexp_desc with
-    | Pexp_record (label_decls, _) when List.length label_decls = 0 -> true
-    | _ -> false
-  in
-  let record = record_from_props ~loc:jsx_expr_loc ~remove_key:true args in
-  let props =
-    if is_empty_record record then empty_record ~loc:jsx_expr_loc else record
-  in
-  let key_prop =
-    args
-    |> List.filter_map (fun (arg_label, e, _opt) ->
-           if "key" = get_label arg_label then Some (arg_label, e) else None)
-  in
-  let jsx_expr, key_and_unit =
-    match (!children_arg, key_prop) with
-    | None, key :: _ ->
-      ( Exp.ident {loc = Location.none; txt = Ldot (element_binding, "jsxKeyed")},
-        [key; (nolabel, unit_expr ~loc:Location.none)] )
-    | None, [] ->
-      (Exp.ident {loc = Location.none; txt = Ldot (element_binding, "jsx")}, [])
-    | Some _, key :: _ ->
-      ( Exp.ident
-          {loc = Location.none; txt = Ldot (element_binding, "jsxsKeyed")},
-        [key; (nolabel, unit_expr ~loc:Location.none)] )
-    | Some _, [] ->
-      (Exp.ident {loc = Location.none; txt = Ldot (element_binding, "jsxs")}, [])
-  in
-  Exp.apply ~loc:jsx_expr_loc ~attrs jsx_expr
-    ([(nolabel, component_name_expr); (nolabel, props)] @ key_and_unit)
-
 let rec recursively_transform_named_args_for_make expr args newtypes core_type =
   match expr.pexp_desc with
   (* TODO: make this show up with a loc. *)
@@ -1392,120 +1090,265 @@ let transform_signature_item ~config item =
         "Only one JSX component call can exist on a component at one time")
   | _ -> [item]
 
-let transform_jsx_call ~config mapper call_expression call_arguments
-    jsx_expr_loc attrs =
-  match call_expression.pexp_desc with
-  | Pexp_ident caller -> (
-    match caller with
-    | {txt = Lident "createElement"; loc} ->
-      Jsx_common.raise_error ~loc
-        "JSX: `createElement` should be preceeded by a module name."
-    (* Foo.createElement(~prop1=foo, ~prop2=bar, ~children=[], ()) *)
-    | {loc; txt = Ldot (module_path, ("createElement" | "make"))} ->
-      transform_uppercase_call3 ~config module_path mapper jsx_expr_loc loc
-        attrs call_arguments
-    (* div(~prop1=foo, ~prop2=bar, ~children=[bla], ()) *)
-    (* turn that into
-       ReactDOM.createElement(~props=ReactDOM.props(~props1=foo, ~props2=bar, ()), [|bla|]) *)
-    | {loc; txt = Lident id} ->
-      transform_lowercase_call3 ~config mapper jsx_expr_loc loc attrs
-        call_arguments id
-    | {txt = Ldot (_, anything_not_create_element_or_make); loc} ->
-      Jsx_common.raise_error ~loc
-        "JSX: the JSX attribute should be attached to a \
-         `YourModuleName.createElement` or `YourModuleName.make` call. We saw \
-         `%s` instead"
-        anything_not_create_element_or_make
-    | {txt = Lapply _; loc} ->
-      (* don't think there's ever a case where this is reached *)
-      Jsx_common.raise_error ~loc
-        "JSX: encountered a weird case while processing the code. Please \
-         report this!")
-  | _ ->
-    Jsx_common.raise_error ~loc:call_expression.pexp_loc
-      "JSX: `createElement` should be preceeded by a simple, direct module \
-       name."
+let starts_with_lowercase s =
+  if String.length s = 0 then false
+  else
+    let c = s.[0] in
+    Char.lowercase_ascii c = c
+
+let starts_with_uppercase s =
+  if String.length s = 0 then false
+  else
+    let c = s.[0] in
+    Char.uppercase_ascii c = c
+
+(* There appear to be slightly different rules of transformation whether the component is upper-, lowercase or a fragment *)
+type componentDescription =
+  | LowercasedComponent
+  | UppercasedComponent
+  | FragmentComponent
+
+let loc_from_prop = function
+  | JSXPropPunning (_, name) -> name.loc
+  | JSXPropValue (name, _, value) ->
+    {name.loc with loc_end = value.pexp_loc.loc_end}
+  | JSXPropSpreading (loc, _) -> loc
+
+let mk_record_from_props mapper (jsx_expr_loc : Location.t) (props : jsx_props)
+    : expression =
+  (* Create an artificial range from the first till the last prop *)
+  let loc =
+    match props with
+    | [] -> jsx_expr_loc
+    | head :: tail ->
+      let rec visit props =
+        match props with
+        | [] -> head
+        | [last] -> last
+        | _ :: rest -> visit rest
+      in
+      let first_item = head |> loc_from_prop in
+      let last_item = visit tail |> loc_from_prop in
+      {
+        loc_start = first_item.loc_start;
+        loc_end = last_item.loc_end;
+        loc_ghost = true;
+      }
+  in
+  (* key should be filtered out *)
+  let props =
+    props
+    |> List.filter (function
+         | JSXPropPunning (_, {txt = "key"}) | JSXPropValue ({txt = "key"}, _, _)
+           ->
+           false
+         | _ -> true)
+  in
+  let props, spread_props =
+    match props with
+    | JSXPropSpreading (_, expr) :: rest ->
+      (rest, Some (mapper.expr mapper expr))
+    | _ -> (props, None)
+  in
 
-let expr ~config mapper expression =
+  let record_fields =
+    props
+    |> List.map (function
+         | JSXPropPunning (is_optional, name) ->
+           ( {txt = Lident name.txt; loc = name.loc},
+             Exp.ident {txt = Lident name.txt; loc = name.loc},
+             is_optional )
+         | JSXPropValue (name, is_optional, value) ->
+           ( {txt = Lident name.txt; loc = name.loc},
+             mapper.expr mapper value,
+             is_optional )
+         | JSXPropSpreading (loc, _) ->
+           (* There can only be one spread expression and it is expected to be the first prop *)
+           Jsx_common.raise_error ~loc
+             "JSX: use {...p} {x: v} not {x: v} {...p} \n\
+             \     multiple spreads {...p} {...p} not allowed.")
+  in
+  match (record_fields, spread_props) with
+  | [], Some spread_props ->
+    {pexp_desc = spread_props.pexp_desc; pexp_loc = loc; pexp_attributes = []}
+  | record_fields, spread_props ->
+    {
+      pexp_desc = Pexp_record (record_fields, spread_props);
+      pexp_loc = loc;
+      pexp_attributes = [];
+    }
+
+let try_find_key_prop (props : jsx_props) : (arg_label * expression) option =
+  props
+  |> List.find_map (function
+       | JSXPropPunning (is_optional, ({txt = "key"} as name)) ->
+         let arg_label = if is_optional then Optional name else Labelled name in
+         Some (arg_label, Exp.ident {txt = Lident "key"; loc = name.loc})
+       | JSXPropValue (({txt = "key"} as name), is_optional, expr) ->
+         let arg_label = if is_optional then Optional name else Labelled name in
+         Some (arg_label, expr)
+       | _ -> None)
+
+let append_children_prop (config : Jsx_common.jsx_config) mapper
+    (component_description : componentDescription) (props : jsx_props)
+    (children : jsx_children) : jsx_props =
+  match children with
+  | JSXChildrenItems [] -> props
+  | JSXChildrenItems [child] | JSXChildrenSpreading child ->
+    let expr =
+      (* I don't quite know why fragment and uppercase don't do this additional ReactDOM.someElement wrapping *)
+      match component_description with
+      | FragmentComponent | UppercasedComponent -> mapper.expr mapper child
+      | LowercasedComponent ->
+        let element_binding =
+          match config.module_ |> String.lowercase_ascii with
+          | "react" -> Lident "ReactDOM"
+          | _generic -> module_access_name config "Elements"
+        in
+        Exp.apply
+          (Exp.ident
+             {txt = Ldot (element_binding, "someElement"); loc = Location.none})
+          [(Nolabel, child)]
+    in
+    let is_optional =
+      match component_description with
+      | LowercasedComponent -> true
+      | FragmentComponent | UppercasedComponent -> false
+    in
+    props
+    @ [
+        JSXPropValue ({txt = "children"; loc = Location.none}, is_optional, expr);
+      ]
+  | JSXChildrenItems xs ->
+    (* this is a hack to support react components that introspect into their children *)
+    props
+    @ [
+        JSXPropValue
+          ( {txt = "children"; loc = Location.none},
+            false,
+            Exp.apply
+              (Exp.ident
+                 {txt = module_access_name config "array"; loc = Location.none})
+              [(Nolabel, Exp.array (List.map (mapper.expr mapper) xs))] );
+      ]
+
+let mk_react_jsx (config : Jsx_common.jsx_config) mapper loc attrs
+    (component_description : componentDescription) (elementTag : expression)
+    (props : jsx_props) (children : jsx_children) : expression =
+  let more_than_one_children =
+    match children with
+    | JSXChildrenSpreading _ -> false
+    | JSXChildrenItems xs -> List.length xs > 1
+  in
+  let props_with_children =
+    append_children_prop config mapper component_description props children
+  in
+  let props_record = mk_record_from_props mapper loc props_with_children in
+  let jsx_expr, key_and_unit =
+    let mk_element_bind (jsx_part : string) : Longident.t =
+      match component_description with
+      | FragmentComponent | UppercasedComponent ->
+        module_access_name config jsx_part
+      | LowercasedComponent ->
+        let element_binding =
+          match config.module_ |> String.lowercase_ascii with
+          | "react" -> Lident "ReactDOM"
+          | _generic -> module_access_name config "Elements"
+        in
+        Ldot (element_binding, jsx_part)
+    in
+    match try_find_key_prop props with
+    | None ->
+      ( Exp.ident
+          {
+            loc = Location.none;
+            txt =
+              mk_element_bind (if more_than_one_children then "jsxs" else "jsx");
+          },
+        [] )
+    | Some key_prop ->
+      ( Exp.ident
+          {
+            loc = Location.none;
+            txt =
+              mk_element_bind
+                (if more_than_one_children then "jsxsKeyed" else "jsxKeyed");
+          },
+        [key_prop; (nolabel, unit_expr ~loc:Location.none)] )
+  in
+  let args = [(nolabel, elementTag); (nolabel, props_record)] @ key_and_unit in
+  Exp.apply ~loc ~attrs jsx_expr args
+
+(* In most situations, the component name is the make function from a module. 
+    However, if the name contains a lowercase letter, it means it probably an external component.
+    In this case, we use the name as is.
+    See tests/syntax_tests/data/ppx/react/externalWithCustomName.res
+*)
+let mk_uppercase_tag_name_expr tag_name =
+  let tag_identifier : Longident.t =
+    if Longident.flatten tag_name.txt |> List.for_all starts_with_uppercase then
+      (* All parts are uppercase, so we append .make *)
+      Ldot (tag_name.txt, "make")
+    else tag_name.txt
+  in
+  Exp.ident ~loc:tag_name.loc {txt = tag_identifier; loc = tag_name.loc}
+
+let expr ~(config : Jsx_common.jsx_config) mapper expression =
   match expression with
-  (* Does the function application have the @JSX attribute? *)
   | {
-   pexp_desc = Pexp_apply {funct = call_expression; args = call_arguments};
-   pexp_attributes;
-   pexp_loc;
+   pexp_desc = Pexp_jsx_element jsx_element;
+   pexp_loc = loc;
+   pexp_attributes = attrs;
   } -> (
-    let jsx_attribute, non_jsx_attributes =
-      List.partition
-        (fun (attribute, _) -> attribute.txt = "JSX")
-        pexp_attributes
-    in
-    match (jsx_attribute, non_jsx_attributes) with
-    (* no JSX attribute *)
-    | [], _ -> default_mapper.expr mapper expression
-    | _, non_jsx_attributes ->
-      transform_jsx_call ~config mapper call_expression call_arguments pexp_loc
-        non_jsx_attributes)
-  (* is it a list with jsx attribute? Reason <>foo</> desugars to [@JSX][foo]*)
-  | {
-      pexp_desc =
-        ( Pexp_construct
-            ({txt = Lident "::"; loc}, Some {pexp_desc = Pexp_tuple _})
-        | Pexp_construct ({txt = Lident "[]"; loc}, None) );
-      pexp_attributes;
-    } as list_items -> (
-    let jsx_attribute, non_jsx_attributes =
-      List.partition
-        (fun (attribute, _) -> attribute.txt = "JSX")
-        pexp_attributes
-    in
-    match (jsx_attribute, non_jsx_attributes) with
-    (* no JSX attribute *)
-    | [], _ -> default_mapper.expr mapper expression
-    | _, non_jsx_attributes ->
-      let loc = {loc with loc_ghost = true} in
+    let loc = {loc with loc_ghost = true} in
+    match jsx_element with
+    | Jsx_fragment {jsx_fragment_children = children} ->
       let fragment =
         Exp.ident ~loc {loc; txt = module_access_name config "jsxFragment"}
       in
-      let children_expr = transform_children_if_list ~mapper list_items in
-      let record_of_children children =
-        Exp.record
-          [(Location.mknoloc (Lident "children"), children, false)]
-          None
-      in
-      let apply_jsx_array expr =
-        Exp.apply
-          (Exp.ident
-             {txt = module_access_name config "array"; loc = Location.none})
-          [(Nolabel, expr)]
-      in
-      let count_of_children = function
-        | {pexp_desc = Pexp_array children} -> List.length children
-        | _ -> 0
-      in
-      let transform_children_to_props children_expr =
-        match children_expr with
-        | {pexp_desc = Pexp_array children} -> (
-          match children with
-          | [] -> empty_record ~loc:Location.none
-          | [child] -> record_of_children child
-          | _ -> record_of_children @@ apply_jsx_array children_expr)
-        | _ -> record_of_children @@ apply_jsx_array children_expr
-      in
-      let args =
-        [
-          (nolabel, fragment);
-          (nolabel, transform_children_to_props children_expr);
-        ]
-      in
-      Exp.apply
-        ~loc (* throw away the [@JSX] attribute and keep the others, if any *)
-        ~attrs:non_jsx_attributes
-        (* ReactDOM.createElement *)
-        (if count_of_children children_expr > 1 then
-           Exp.ident ~loc {loc; txt = module_access_name config "jsxs"}
-         else Exp.ident ~loc {loc; txt = module_access_name config "jsx"})
-        args)
-  (* Delegate to the default mapper, a deep identity traversal *)
+      mk_react_jsx config mapper loc attrs FragmentComponent fragment []
+        children
+    | Jsx_unary_element
+        {jsx_unary_element_tag_name = tag_name; jsx_unary_element_props = props}
+      ->
+      let name = Longident.flatten tag_name.txt |> String.concat "." in
+      if starts_with_lowercase name then
+        (* For example 'input' *)
+        let component_name_expr = constant_string ~loc:tag_name.loc name in
+        mk_react_jsx config mapper loc attrs LowercasedComponent
+          component_name_expr props (JSXChildrenItems [])
+      else if starts_with_uppercase name then
+        (* MyModule.make *)
+        let make_id = mk_uppercase_tag_name_expr tag_name in
+        mk_react_jsx config mapper loc attrs UppercasedComponent make_id props
+          (JSXChildrenItems [])
+      else
+        Jsx_common.raise_error ~loc
+          "JSX: element name is neither upper- or lowercase, got \"%s\""
+          (Longident.flatten tag_name.txt |> String.concat ".")
+    | Jsx_container_element
+        {
+          jsx_container_element_tag_name_start = tag_name;
+          jsx_container_element_props = props;
+          jsx_container_element_children = children;
+        } ->
+      let name = Longident.flatten tag_name.txt |> String.concat "." in
+      (* For example: <div> <h1></h1> <br /> </div>
+         This has an impact if we want to use ReactDOM.jsx or ReactDOM.jsxs
+           *)
+      if starts_with_lowercase name then
+        let component_name_expr = constant_string ~loc:tag_name.loc name in
+        mk_react_jsx config mapper loc attrs LowercasedComponent
+          component_name_expr props children
+      else if starts_with_uppercase name then
+        (* MyModule.make *)
+        let make_id = mk_uppercase_tag_name_expr tag_name in
+        mk_react_jsx config mapper loc attrs UppercasedComponent make_id props
+          children
+      else
+        Jsx_common.raise_error ~loc
+          "JSX: element name is neither upper- or lowercase, got \"%s\""
+          (Longident.flatten tag_name.txt |> String.concat "."))
   | e -> default_mapper.expr mapper e
 
 let module_binding ~(config : Jsx_common.jsx_config) mapper module_binding =
diff --git a/compiler/syntax/src/jsx_v4.mli b/compiler/syntax/src/jsx_v4.mli
new file mode 100644
index 0000000000..b5823409bd
--- /dev/null
+++ b/compiler/syntax/src/jsx_v4.mli
@@ -0,0 +1,8 @@
+open Parsetree
+
+val jsx_mapper :
+  config:Jsx_common.jsx_config ->
+  (Ast_mapper.mapper -> expression -> expression)
+  * (Ast_mapper.mapper -> module_binding -> module_binding)
+  * (signature_item -> signature_item list)
+  * (structure_item -> structure_item list)
diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml
index d53d91fae9..70f1e298bf 100644
--- a/compiler/syntax/src/res_ast_debugger.ml
+++ b/compiler/syntax/src/res_ast_debugger.ml
@@ -708,9 +708,49 @@ module SexpAst = struct
       | Pexp_extension ext ->
         Sexp.list [Sexp.atom "Pexp_extension"; extension ext]
       | Pexp_await e -> Sexp.list [Sexp.atom "Pexp_await"; expression e]
+      | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
+        let xs =
+          match children with
+          | JSXChildrenSpreading e -> [e]
+          | JSXChildrenItems xs -> xs
+        in
+        Sexp.list
+          [
+            Sexp.atom "Pexp_jsx_fragment"; Sexp.list (map_empty ~f:expression xs);
+          ]
+      | Pexp_jsx_element (Jsx_unary_element {jsx_unary_element_props = props})
+        ->
+        Sexp.list
+          [
+            Sexp.atom "Pexp_jsx_unary_element";
+            Sexp.list (map_empty ~f:jsx_prop props);
+          ]
+      | Pexp_jsx_element
+          (Jsx_container_element
+             {
+               jsx_container_element_props = props;
+               jsx_container_element_children = children;
+             }) ->
+        let xs =
+          match children with
+          | JSXChildrenSpreading e -> [e]
+          | JSXChildrenItems xs -> xs
+        in
+        Sexp.list
+          [
+            Sexp.atom "Pexp_jsx_container_element";
+            Sexp.list (map_empty ~f:jsx_prop props);
+            Sexp.list (map_empty ~f:expression xs);
+          ]
     in
     Sexp.list [Sexp.atom "expression"; desc]
 
+  and jsx_prop = function
+    | JSXPropPunning (_, name) -> Sexp.atom name.txt
+    | JSXPropValue (name, _, expr) ->
+      Sexp.list [Sexp.atom name.txt; expression expr]
+    | JSXPropSpreading (_, expr) -> expression expr
+
   and case c =
     Sexp.list
       [
diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml
index c003d04ff6..99308f2341 100644
--- a/compiler/syntax/src/res_comments_table.ml
+++ b/compiler/syntax/src/res_comments_table.ml
@@ -24,25 +24,29 @@ let copy tbl =
 
 let empty = make ()
 
+let rec list_last = function
+  | [] -> failwith "list_last: empty list"
+  | [x] -> x
+  | _ :: rest -> list_last rest
+
+let print_location (k : Warnings.loc) =
+  Doc.concat
+    [
+      Doc.lbracket;
+      Doc.text (string_of_int k.loc_start.pos_lnum);
+      Doc.text ":";
+      Doc.text (string_of_int (k.loc_start.pos_cnum - k.loc_start.pos_bol));
+      Doc.text "-";
+      Doc.text (string_of_int k.loc_end.pos_lnum);
+      Doc.text ":";
+      Doc.text (string_of_int (k.loc_end.pos_cnum - k.loc_end.pos_bol));
+      Doc.rbracket;
+    ]
+
 let print_entries tbl =
-  let open Location in
   Hashtbl.fold
     (fun (k : Location.t) (v : Comment.t list) acc ->
-      let loc =
-        Doc.concat
-          [
-            Doc.lbracket;
-            Doc.text (string_of_int k.loc_start.pos_lnum);
-            Doc.text ":";
-            Doc.text
-              (string_of_int (k.loc_start.pos_cnum - k.loc_start.pos_bol));
-            Doc.text "-";
-            Doc.text (string_of_int k.loc_end.pos_lnum);
-            Doc.text ":";
-            Doc.text (string_of_int (k.loc_end.pos_cnum - k.loc_end.pos_bol));
-            Doc.rbracket;
-          ]
-      in
+      let loc = print_location k in
       let doc =
         Doc.breakable_group ~force_break:true
           (Doc.concat
@@ -86,6 +90,28 @@ let attach tbl loc comments =
   | [] -> ()
   | comments -> Hashtbl.replace tbl loc comments
 
+(* Partitions a list of comments into three groups based on their position relative to a location:
+ * - leading: comments that end before the location's start position
+ * - inside: comments that overlap with the location
+ * - trailing: comments that start after the location's end position
+ *
+ * For example, given code:
+ *   /* comment1 */ let x = /* comment2 */ 5 /* comment3 */
+ *
+ * When splitting around the location of `x = 5`:
+ * - leading: [comment1]
+ * - inside: [comment2]  
+ * - trailing: [comment3]
+ * 
+ * This is the primary comment partitioning function used for associating comments
+ * with AST nodes during the tree traversal.
+ *
+ * Parameters:
+ * - comments: list of comments to partition
+ * - loc: location to split around
+ *
+ * Returns: (leading_comments, inside_comments, trailing_comments)
+ *)
 let partition_by_loc comments loc =
   let rec loop (leading, inside, trailing) comments =
     let open Location in
@@ -101,6 +127,23 @@ let partition_by_loc comments loc =
   in
   loop ([], [], []) comments
 
+(* Splits a list of comments into two groups based on their position relative to a location:
+ * - leading: comments that end before the location's start
+ * - trailing: comments that start at or after the location's start
+ *
+ * For example, given the code:
+ *   /* comment1 */ let /* comment2 */ x = 1 /* comment3 */
+ *
+ * When splitting around `x`'s location:
+ * - leading: [comment1, comment2]
+ * - trailing: [comment3]
+ *
+ * Parameters:
+ * - comments: list of comments to partition
+ * - loc: location to split around
+ *
+ * Returns: (leading_comments, trailing_comments)
+ *)
 let partition_leading_trailing comments loc =
   let rec loop (leading, trailing) comments =
     let open Location in
@@ -114,6 +157,28 @@ let partition_leading_trailing comments loc =
   in
   loop ([], []) comments
 
+(* Splits comments into two groups based on whether they start on the same line as a location's end position.
+ *
+ * This is particularly useful for handling comments that appear on the same line as a syntax element
+ * versus comments that appear on subsequent lines.
+ *
+ * For example, given code:
+ *   let x = 1 /* same line comment */
+ *   /* different line comment */
+ *
+ * When splitting around `x = 1`'s location:
+ * - on_same_line: [/* same line comment */]
+ * - on_other_line: [/* different line comment */]
+ *
+ * This function is often used for formatting decisions where comments on the same line
+ * should be treated differently than comments on different lines (like in JSX elements).
+ *
+ * Parameters:
+ * - loc: location to compare line numbers against
+ * - comments: list of comments to partition
+ *
+ * Returns: (on_same_line_comments, on_other_line_comments)
+ *)
 let partition_by_on_same_line loc comments =
   let rec loop (on_same_line, on_other_line) comments =
     let open Location in
@@ -142,6 +207,92 @@ let partition_adjacent_trailing loc1 comments =
   in
   loop ~prev_end_pos:loc1.loc_end [] comments
 
+(* Splits comments that follow a location but come before another token.
+ * This is particularly useful for handling comments between two tokens
+ * where traditional leading/trailing partitioning isn't precise enough.
+ *
+ * For example, given code:
+ *   let x = 1 /* comment */ + 2
+ *
+ * When splitting comments between `1` and `+`:
+ * - first_part: [/* comment */]  (comments on same line as loc and before next_token)
+ * - rest: []                     (remaining comments)
+ *
+ * Parameters:
+ * - loc: location of the reference token
+ * - next_token: location of the next token
+ * - comments: list of comments to partition
+ *
+ * Returns: (first_part, rest) where first_part contains comments on the same line as loc
+ * that appear entirely before next_token, and rest contains all other comments.
+ *
+ * This function is useful for precisely attaching comments between specific tokens
+ * in constructs like JSX props, function arguments, and other multi-token expressions.
+ *)
+let partition_adjacent_trailing_before_next_token_on_same_line
+    (loc : Warnings.loc) (next_token : Warnings.loc) (comments : Comment.t list)
+    : Comment.t list * Comment.t list =
+  let open Location in
+  let open Lexing in
+  let rec loop after_loc comments =
+    match comments with
+    | [] -> (List.rev after_loc, [])
+    | comment :: rest ->
+      (* Check if the comment is on the same line as the loc, and is entirely before the next_token *)
+      let cmt_loc = Comment.loc comment in
+      if
+        (* Same line *)
+        cmt_loc.loc_start.pos_lnum == loc.loc_end.pos_lnum
+        (* comment after loc *)
+        && cmt_loc.loc_start.pos_cnum > loc.loc_end.pos_cnum
+        (* comment before next_token *)
+        && cmt_loc.loc_end.pos_cnum <= next_token.loc_start.pos_cnum
+      then loop (comment :: after_loc) rest
+      else (List.rev after_loc, comments)
+  in
+  loop [] comments
+
+(* Extracts comments that appear between two specified line numbers in a source file.
+ *
+ * This function is particularly useful for handling comments that should be preserved
+ * between two syntax elements that appear on different lines, such as comments between
+ * opening and closing tags in JSX elements.
+ *
+ * For example, given code:
+ *   <div>
+ *     // comment 1
+ *     // comment 2
+ *   </div>
+ *
+ * When calling partition_between_lines with the line numbers of the opening and closing tags:
+ * - between_comments: [comment 1, comment 2]
+ * - rest: any comments that appear before or after the specified lines
+ *
+ * Parameters:
+ * - start_line: the line number after which to start collecting comments
+ * - end_line: the line number before which to stop collecting comments
+ * - comments: list of comments to partition
+ *
+ * Returns: (between_comments, rest) where between_comments contains all comments
+ * entirely between the start_line and end_line, and rest contains all other comments.
+ *)
+let partition_between_lines start_line end_line comments =
+  let open Location in
+  let open Lexing in
+  let rec loop between_comments comments =
+    match comments with
+    | [] -> (List.rev between_comments, [])
+    | comment :: rest ->
+      (* Check if the comment is between the start_line and end_line *)
+      let cmt_loc = Comment.loc comment in
+      if
+        cmt_loc.loc_start.pos_lnum > start_line
+        && cmt_loc.loc_end.pos_lnum < end_line
+      then loop (comment :: between_comments) rest
+      else (List.rev between_comments, comments)
+  in
+  loop [] comments
+
 let rec collect_list_patterns acc pattern =
   let open Parsetree in
   match pattern.ppat_desc with
@@ -352,6 +503,7 @@ type node =
   | StructureItem of Parsetree.structure_item
   | TypeDeclaration of Parsetree.type_declaration
   | ValueBinding of Parsetree.value_binding
+  | JsxProp of Parsetree.jsx_prop
 
 let get_loc node =
   let open Parsetree in
@@ -392,6 +544,7 @@ let get_loc node =
   | StructureItem si -> si.pstr_loc
   | TypeDeclaration td -> td.ptype_loc
   | ValueBinding vb -> vb.pvb_loc
+  | JsxProp prop -> ParsetreeViewer.get_jsx_prop_loc prop
 
 let rec walk_structure s t comments =
   match s with
@@ -571,6 +724,7 @@ and walk_node node tbl comments =
   | StructureItem si -> walk_structure_item si tbl comments
   | TypeDeclaration td -> walk_type_declaration td tbl comments
   | ValueBinding vb -> walk_value_binding vb tbl comments
+  | JsxProp prop -> walk_jsx_prop prop tbl comments
 
 and walk_list : ?prev_loc:Location.t -> node list -> t -> Comment.t list -> unit
     =
@@ -1398,67 +1552,21 @@ and walk_expression expr t comments =
         walk_expression call_expr t inside;
         after)
     in
-    if ParsetreeViewer.is_jsx_expression expr then (
-      let props =
-        arguments
-        |> List.filter (fun (label, _) ->
-               match label with
-               | Asttypes.Labelled {txt = "children"} -> false
-               | Asttypes.Nolabel -> false
-               | _ -> true)
-      in
-      let maybe_children =
-        arguments
-        |> List.find_opt (fun (label, _) ->
-               match label with
-               | Asttypes.Labelled {txt = "children"} -> true
-               | _ -> false)
-      in
-      match maybe_children with
-      (* There is no need to deal with this situation as the children cannot be NONE *)
-      | None -> ()
-      | Some (_, children) ->
-        let leading, inside, _ = partition_by_loc after children.pexp_loc in
-        if props = [] then
-          (* All comments inside a tag are trailing comments of the tag if there are no props
-             <A
-             // comment
-             // comment
-             />
-          *)
-          let after_expr, _ =
-            partition_adjacent_trailing call_expr.pexp_loc after
-          in
-          attach t.trailing call_expr.pexp_loc after_expr
-        else
-          walk_list
-            (props
-            |> List.map (fun (lbl, expr) ->
-                   let loc =
-                     match lbl with
-                     | Asttypes.Labelled {loc} | Optional {loc} ->
-                       {loc with loc_end = expr.Parsetree.pexp_loc.loc_end}
-                     | _ -> expr.pexp_loc
-                   in
-                   ExprArgument {expr; loc}))
-            t leading;
-        walk_expression children t inside)
-    else
-      let after_expr, rest =
-        partition_adjacent_trailing call_expr.pexp_loc after
-      in
-      attach t.trailing call_expr.pexp_loc after_expr;
-      walk_list
-        (arguments
-        |> List.map (fun (lbl, expr) ->
-               let loc =
-                 match lbl with
-                 | Asttypes.Labelled {loc} | Optional {loc} ->
-                   {loc with loc_end = expr.Parsetree.pexp_loc.loc_end}
-                 | _ -> expr.pexp_loc
-               in
-               ExprArgument {expr; loc}))
-        t rest
+    let after_expr, rest =
+      partition_adjacent_trailing call_expr.pexp_loc after
+    in
+    attach t.trailing call_expr.pexp_loc after_expr;
+    walk_list
+      (arguments
+      |> List.map (fun (lbl, expr) ->
+             let loc =
+               match lbl with
+               | Asttypes.Labelled {loc} | Optional {loc} ->
+                 {loc with loc_end = expr.Parsetree.pexp_loc.loc_end}
+               | _ -> expr.pexp_loc
+             in
+             ExprArgument {expr; loc}))
+      t rest
   | Pexp_fun _ | Pexp_newtype _ -> (
     let _, parameters, return_expr = fun_expr expr in
     let comments =
@@ -1508,7 +1616,169 @@ and walk_expression expr t comments =
         attach t.leading return_expr.pexp_loc leading;
         walk_expression return_expr t inside;
         attach t.trailing return_expr.pexp_loc trailing)
-  | _ -> ()
+  | Pexp_jsx_element
+      (Jsx_fragment
+         {
+           jsx_fragment_opening = opening_greater_than;
+           jsx_fragment_children = children;
+           jsx_fragment_closing = _closing_lesser_than;
+         }) ->
+    let opening_token = {expr.pexp_loc with loc_end = opening_greater_than} in
+    let on_same_line, rest = partition_by_on_same_line opening_token comments in
+    attach t.trailing opening_token on_same_line;
+    let exprs =
+      match children with
+      | Parsetree.JSXChildrenSpreading e -> [e]
+      | Parsetree.JSXChildrenItems xs -> xs
+    in
+    let xs = exprs |> List.map (fun e -> Expression e) in
+    walk_list xs t rest
+  | Pexp_jsx_element
+      (Jsx_unary_element
+         {
+           jsx_unary_element_tag_name = tag_name;
+           jsx_unary_element_props = props;
+         }) -> (
+    let closing_token_loc =
+      ParsetreeViewer.unary_element_closing_token expr.pexp_loc
+    in
+
+    let after_opening_tag_name, rest =
+      (* Either the first prop or the closing /> token *)
+      let next_token =
+        match props with
+        | [] -> closing_token_loc
+        | head :: _ -> ParsetreeViewer.get_jsx_prop_loc head
+      in
+      partition_adjacent_trailing_before_next_token_on_same_line tag_name.loc
+        next_token comments
+    in
+
+    (* Only attach comments to the element name if they are on the same line *)
+    attach t.trailing tag_name.loc after_opening_tag_name;
+    match props with
+    | [] ->
+      let before_closing_token, _rest =
+        partition_leading_trailing rest closing_token_loc
+      in
+      (* attach comments to the closing /> token *)
+      attach t.leading closing_token_loc before_closing_token
+      (* the _rest comments are going to be attached after the entire expression,
+         dealt with in the parent node. *)
+    | props ->
+      let comments_for_props, _rest =
+        partition_leading_trailing rest closing_token_loc
+      in
+      let prop_nodes = List.map (fun prop -> JsxProp prop) props in
+      walk_list prop_nodes t comments_for_props)
+  | Pexp_jsx_element
+      (Jsx_container_element
+         {
+           jsx_container_element_tag_name_start = tag_name_start;
+           jsx_container_element_props = props;
+           jsx_container_element_opening_tag_end = opening_greater_than;
+           jsx_container_element_children = children;
+           jsx_container_element_closing_tag = closing_tag;
+         }) -> (
+    let opening_greater_than_loc =
+      {
+        loc_start = opening_greater_than;
+        loc_end = opening_greater_than;
+        loc_ghost = false;
+      }
+    in
+    let after_opening_tag_name, rest =
+      (* Either the first prop or the closing > token *)
+      let next_token =
+        match props with
+        | [] -> opening_greater_than_loc
+        | head :: _ -> ParsetreeViewer.get_jsx_prop_loc head
+      in
+      partition_adjacent_trailing_before_next_token_on_same_line
+        tag_name_start.loc next_token comments
+    in
+    (* Only attach comments to the element name if they are on the same line *)
+    attach t.trailing tag_name_start.loc after_opening_tag_name;
+    let rest =
+      match props with
+      | [] ->
+        let before_greater_than, rest =
+          partition_leading_trailing rest opening_greater_than_loc
+        in
+        (* attach comments to the closing > token *)
+        attach t.leading opening_greater_than_loc before_greater_than;
+        rest
+      | props ->
+        let comments_for_props, rest =
+          partition_leading_trailing rest opening_greater_than_loc
+        in
+        let prop_nodes = List.map (fun prop -> JsxProp prop) props in
+        walk_list prop_nodes t comments_for_props;
+        rest
+    in
+
+    (* comments after '>' on the same line should be attached to '>' *)
+    let after_opening_greater_than, rest =
+      partition_by_on_same_line opening_greater_than_loc rest
+    in
+    attach t.trailing opening_greater_than_loc after_opening_greater_than;
+
+    let comments_for_children, _rest =
+      match closing_tag with
+      | None -> (rest, [])
+      | Some closing_tag ->
+        let closing_tag_loc =
+          ParsetreeViewer.container_element_closing_tag_loc closing_tag
+        in
+        partition_leading_trailing rest closing_tag_loc
+    in
+    match children with
+    | Parsetree.JSXChildrenItems [] -> (
+      (* attach all comments to the closing tag if there are no children *)
+      match closing_tag with
+      | None ->
+        (* if there is no closing tag, the comments will attached after the expression *)
+        ()
+      | Some closing_tag ->
+        let closing_tag_loc =
+          ParsetreeViewer.container_element_closing_tag_loc closing_tag
+        in
+        if
+          opening_greater_than_loc.loc_end.pos_lnum
+          < closing_tag_loc.loc_start.pos_lnum + 1
+        then (
+          (* In this case, there are no children but there are comments between the opening and closing tag,
+             We can attach these the inside table, to easily print them later as indented comments
+             For example:
+             <div>
+                // comment 1
+                // comment 2
+            </div>
+          *)
+          let inside_comments, leading_for_closing_tag =
+            partition_between_lines opening_greater_than_loc.loc_end.pos_lnum
+              closing_tag_loc.loc_start.pos_lnum comments_for_children
+          in
+          attach t.inside expr.pexp_loc inside_comments;
+          attach t.leading closing_tag_loc leading_for_closing_tag)
+        else
+          (* if the closing tag is on the same line, attach comments to the opening tag *)
+          attach t.leading closing_tag_loc comments_for_children)
+    | children ->
+      let children_nodes =
+        match children with
+        | Parsetree.JSXChildrenSpreading e -> [Expression e]
+        | Parsetree.JSXChildrenItems xs -> List.map (fun e -> Expression e) xs
+      in
+
+      walk_list children_nodes t comments_for_children
+    (* It is less likely that there are comments inside the closing tag, 
+       so we don't process them right now,
+       if you ever need this, feel free to update process _rest. 
+       Comments after the closing tag will already be taking into account by the parent node. *)
+    )
+  | Pexp_await expr -> walk_expression expr t comments
+  | Pexp_send _ -> ()
 
 and walk_expr_parameter (_attrs, _argLbl, expr_opt, pattern) t comments =
   let leading, inside, trailing = partition_by_loc comments pattern.ppat_loc in
@@ -2015,3 +2285,23 @@ and walk_payload payload t comments =
   match payload with
   | PStr s -> walk_structure s t comments
   | _ -> ()
+
+and walk_jsx_prop prop t comments =
+  match prop with
+  | Parsetree.JSXPropPunning _ ->
+    (* this is covered by walk_list, as the location for the prop is cover there. *)
+    ()
+  | Parsetree.JSXPropValue (name, _, value) ->
+    if name.loc.loc_end.pos_lnum == value.pexp_loc.loc_start.pos_lnum then
+      (* In the rare case that comments are found between name=value,
+         where both are on the same line,
+         we assign them to the value, and not to the name. *)
+      walk_list [Expression value] t comments
+    else
+      (* otherwise we attach comments that come directly after the name to the name *)
+      let after_name, rest = partition_by_on_same_line name.loc comments in
+      attach t.trailing name.loc after_name;
+      walk_list [Expression value] t rest
+  | Parsetree.JSXPropSpreading (_, value) ->
+    (* We assign all comments to the spreaded expression *)
+    walk_list [Expression value] t comments
diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml
index 97b76717e3..03ed02f450 100644
--- a/compiler/syntax/src/res_core.ml
+++ b/compiler/syntax/src/res_core.ml
@@ -155,7 +155,6 @@ module InExternal = struct
   let status = ref false
 end
 
-let jsx_attr = (Location.mknoloc "JSX", Parsetree.PStr [])
 let ternary_attr = (Location.mknoloc "res.ternary", Parsetree.PStr [])
 let if_let_attr = (Location.mknoloc "res.iflet", Parsetree.PStr [])
 let make_await_attr loc = (Location.mkloc "res.await" loc, Parsetree.PStr [])
@@ -445,28 +444,6 @@ let make_unary_expr start_pos token_end token operand =
       [(Nolabel, operand)]
   | _ -> operand
 
-let make_list_expression loc seq ext_opt =
-  let rec handle_seq = function
-    | [] -> (
-      match ext_opt with
-      | Some ext -> ext
-      | None ->
-        let loc = {loc with Location.loc_ghost = true} in
-        let nil = Location.mkloc (Longident.Lident "[]") loc in
-        Ast_helper.Exp.construct ~loc nil None)
-    | e1 :: el ->
-      let exp_el = handle_seq el in
-      let loc =
-        mk_loc e1.Parsetree.pexp_loc.Location.loc_start exp_el.pexp_loc.loc_end
-      in
-      let arg = Ast_helper.Exp.tuple ~loc [e1; exp_el] in
-      Ast_helper.Exp.construct ~loc
-        (Location.mkloc (Longident.Lident "::") loc)
-        (Some arg)
-  in
-  let expr = handle_seq seq in
-  {expr with pexp_loc = loc}
-
 let make_list_pattern loc seq ext_opt =
   let rec handle_seq = function
     | [] ->
@@ -767,7 +744,8 @@ let parse_module_long_ident ~lowercase p =
   (* Parser.eatBreadcrumb p; *)
   module_ident
 
-let verify_jsx_opening_closing_name p name_expr =
+let verify_jsx_opening_closing_name p
+    (name_longident : Longident.t Location.loc) : bool =
   let closing =
     match p.Parser.token with
     | Lident lident ->
@@ -776,27 +754,13 @@ let verify_jsx_opening_closing_name p name_expr =
     | Uident _ -> (parse_module_long_ident ~lowercase:true p).txt
     | _ -> Longident.Lident ""
   in
-  match name_expr.Parsetree.pexp_desc with
-  | Pexp_ident opening_ident ->
-    let opening =
-      let without_create_element =
-        Longident.flatten opening_ident.txt
-        |> List.filter (fun s -> s <> "createElement")
-      in
-      match Longident.unflatten without_create_element with
-      | Some li -> li
-      | None -> Longident.Lident ""
-    in
-    opening = closing
-  | _ -> assert false
+  let opening = name_longident.txt in
+  opening = closing
 
-let string_of_pexp_ident name_expr =
-  match name_expr.Parsetree.pexp_desc with
-  | Pexp_ident opening_ident ->
-    Longident.flatten opening_ident.txt
-    |> List.filter (fun s -> s <> "createElement")
-    |> String.concat "."
-  | _ -> ""
+let string_of_longident (longindent : Longident.t Location.loc) =
+  Longident.flatten longindent.txt
+  (* |> List.filter (fun s -> s <> "createElement") *)
+  |> String.concat "."
 
 (* open-def ::=
  *   | open module-path
@@ -2586,36 +2550,106 @@ and parse_let_bindings ~attrs ~start_pos p =
   in
   (rec_flag, loop p [first])
 
-(*
- * div -> div
- * Foo -> Foo.createElement
- * Foo.Bar -> Foo.Bar.createElement
- *)
-and parse_jsx_name p =
-  let longident =
+and parse_jsx_name p : Longident.t Location.loc =
+  match p.Parser.token with
+  | Lident ident ->
+    let ident_start = p.start_pos in
+    let ident_end = p.end_pos in
+    Parser.next p;
+    let loc = mk_loc ident_start ident_end in
+    Location.mkloc (Longident.Lident ident) loc
+  | Uident _ ->
+    let longident = parse_module_long_ident ~lowercase:true p in
+    longident
+  | _ ->
+    let msg =
+      "A jsx name must be a lowercase or uppercase name, like: div in <div /> \
+       or Navbar in <Navbar />"
+    in
+    Parser.err p (Diagnostics.message msg);
+    Location.mknoloc (Longident.Lident "_")
+
+and parse_jsx_opening_or_self_closing_element (* start of the opening < *)
+    ~start_pos p : Parsetree.expression =
+  let name = parse_jsx_name p in
+  let jsx_props = parse_jsx_props p in
+  match p.Parser.token with
+  | Forwardslash ->
+    (* <foo a=b /> *)
+    Parser.next p;
+    Scanner.pop_mode p.scanner Jsx;
+    let jsx_end_pos = p.end_pos in
+    Parser.expect GreaterThan p;
+    let loc = mk_loc start_pos jsx_end_pos in
+    Ast_helper.Exp.jsx_unary_element ~loc name jsx_props
+  | GreaterThan -> (
+    (* <foo a=b> bar </foo> *)
+    let opening_tag_end = p.Parser.start_pos in
+    Parser.next p;
+    let children = parse_jsx_children p in
+    let closing_tag_start =
+      match p.token with
+      | LessThanSlash ->
+        let pos = p.start_pos in
+        Parser.next p;
+        Some pos
+      | LessThan ->
+        let pos = p.start_pos in
+        Parser.next p;
+        Parser.expect Forwardslash p;
+        Some pos
+      | token when Grammar.is_structure_item_start token -> None
+      | _ ->
+        Parser.expect LessThanSlash p;
+        None
+    in
     match p.Parser.token with
-    | Lident ident ->
-      let ident_start = p.start_pos in
-      let ident_end = p.end_pos in
-      Parser.next p;
-      let loc = mk_loc ident_start ident_end in
-      Location.mkloc (Longident.Lident ident) loc
-    | Uident _ ->
-      let longident = parse_module_long_ident ~lowercase:true p in
-      Location.mkloc
-        (Longident.Ldot (longident.txt, "createElement"))
-        longident.loc
-    | _ ->
-      let msg =
-        "A jsx name must be a lowercase or uppercase name, like: div in <div \
-         /> or Navbar in <Navbar />"
+    | (Lident _ | Uident _) when verify_jsx_opening_closing_name p name ->
+      let end_tag_name = {name with loc = mk_loc p.start_pos p.end_pos} in
+      Scanner.pop_mode p.scanner Jsx;
+      let closing_tag_end = p.start_pos in
+      Parser.expect GreaterThan p;
+      let loc = mk_loc start_pos p.prev_end_pos in
+      let closing_tag =
+        closing_tag_start
+        |> Option.map (fun closing_tag_start ->
+               {
+                 Parsetree.jsx_closing_container_tag_start = closing_tag_start;
+                 jsx_closing_container_tag_name = end_tag_name;
+                 jsx_closing_container_tag_end = closing_tag_end;
+               })
       in
-      Parser.err p (Diagnostics.message msg);
-      Location.mknoloc (Longident.Lident "_")
-  in
-  Ast_helper.Exp.ident ~loc:longident.loc longident
 
-and parse_jsx_opening_or_self_closing_element ~start_pos p =
+      Ast_helper.Exp.jsx_container_element ~loc name jsx_props opening_tag_end
+        children closing_tag
+    | token ->
+      Scanner.pop_mode p.scanner Jsx;
+      let () =
+        if Grammar.is_structure_item_start token then
+          let closing = "</" ^ string_of_longident name ^ ">" in
+          let msg = Diagnostics.message ("Missing " ^ closing) in
+          Parser.err ~start_pos ~end_pos:p.prev_end_pos p msg
+        else
+          let opening = "</" ^ string_of_longident name ^ ">" in
+          let msg =
+            "Closing jsx name should be the same as the opening name. Did you \
+             mean " ^ opening ^ " ?"
+          in
+          Parser.err ~start_pos ~end_pos:p.prev_end_pos p
+            (Diagnostics.message msg);
+          Parser.expect GreaterThan p
+      in
+      Ast_helper.Exp.jsx_container_element
+        ~loc:(mk_loc start_pos p.prev_end_pos)
+        name jsx_props opening_tag_end children None)
+  | token ->
+    Scanner.pop_mode p.scanner Jsx;
+    Parser.err p (Diagnostics.unexpected token p.breadcrumbs);
+    Ast_helper.Exp.jsx_unary_element
+      ~loc:(mk_loc start_pos p.prev_end_pos)
+      name jsx_props
+
+(* and parse_jsx_opening_or_self_closing_element_old ~start_pos p =
   let jsx_start_pos = p.Parser.start_pos in
   let name = parse_jsx_name p in
   let jsx_props = parse_jsx_props p in
@@ -2629,7 +2663,7 @@ and parse_jsx_opening_or_self_closing_element ~start_pos p =
       Scanner.pop_mode p.scanner Jsx;
       Parser.expect GreaterThan p;
       let loc = mk_loc children_start_pos children_end_pos in
-      make_list_expression loc [] None (* no children *)
+      Ast_helper.Exp.make_list_expression loc [] None (* no children *)
     | GreaterThan -> (
       (* <foo a=b> bar </foo> *)
       let children_start_pos = p.Parser.start_pos in
@@ -2652,7 +2686,7 @@ and parse_jsx_opening_or_self_closing_element ~start_pos p =
         let loc = mk_loc children_start_pos children_end_pos in
         match (spread, children) with
         | true, child :: _ -> child
-        | _ -> make_list_expression loc children None)
+        | _ -> Ast_helper.Exp.make_list_expression loc children None)
       | token -> (
         Scanner.pop_mode p.scanner Jsx;
         let () =
@@ -2673,11 +2707,11 @@ and parse_jsx_opening_or_self_closing_element ~start_pos p =
         let loc = mk_loc children_start_pos children_end_pos in
         match (spread, children) with
         | true, child :: _ -> child
-        | _ -> make_list_expression loc children None))
+        | _ -> Ast_helper.Exp.make_list_expression loc children None))
     | token ->
       Scanner.pop_mode p.scanner Jsx;
       Parser.err p (Diagnostics.unexpected token p.breadcrumbs);
-      make_list_expression Location.none [] None
+      Ast_helper.Exp.make_list_expression Location.none [] None
   in
   let jsx_end_pos = p.prev_end_pos in
   let loc = mk_loc jsx_start_pos jsx_end_pos in
@@ -2692,7 +2726,7 @@ and parse_jsx_opening_or_self_closing_element ~start_pos p =
                (Location.mknoloc (Longident.Lident "()"))
                None );
          ];
-       ])
+       ]) *)
 
 (*
  *  jsx ::=
@@ -2713,28 +2747,33 @@ and parse_jsx p =
       parse_jsx_opening_or_self_closing_element ~start_pos p
     | GreaterThan ->
       (* fragment: <> foo </> *)
-      parse_jsx_fragment p
-    | _ -> parse_jsx_name p
+      parse_jsx_fragment start_pos p
+    | _ ->
+      let longident = parse_jsx_name p in
+      Ast_helper.Exp.ident ~loc:longident.loc longident
   in
   Parser.eat_breadcrumb p;
-  {jsx_expr with pexp_attributes = [jsx_attr]}
+  jsx_expr
 
 (*
  * jsx-fragment ::=
  *  | <> </>
  *  | <> jsx-children </>
  *)
-and parse_jsx_fragment p =
+and parse_jsx_fragment start_pos p =
   let children_start_pos = p.Parser.start_pos in
   Parser.expect GreaterThan p;
-  let _spread, children = parse_jsx_children p in
+  let children = parse_jsx_children p in
   let children_end_pos = p.Parser.start_pos in
   if p.token = LessThan then p.token <- Scanner.reconsider_less_than p.scanner;
   Parser.expect LessThanSlash p;
   Scanner.pop_mode p.scanner Jsx;
+  let end_pos = p.Parser.end_pos in
   Parser.expect GreaterThan p;
-  let loc = mk_loc children_start_pos children_end_pos in
-  make_list_expression loc children None
+  (* location is from starting < till closing >  *)
+  let loc = mk_loc start_pos end_pos in
+  Ast_helper.Exp.jsx_fragment ~attrs:[] ~loc children_start_pos children
+    children_end_pos
 
 (*
  * jsx-prop ::=
@@ -2744,17 +2783,13 @@ and parse_jsx_fragment p =
  *   |  lident = ?jsx_expr
  *   |  {...jsx_expr}
  *)
-and parse_jsx_prop p =
+and parse_jsx_prop p : Parsetree.jsx_prop option =
   match p.Parser.token with
   | Question | Lident _ -> (
     let optional = Parser.optional p Question in
     let name, loc = parse_lident p in
     (* optional punning: <foo ?a /> *)
-    if optional then
-      Some
-        ( Asttypes.Optional {txt = name; loc},
-          Ast_helper.Exp.ident ~loc (Location.mkloc (Longident.Lident name) loc)
-        )
+    if optional then Some (Parsetree.JSXPropPunning (true, {txt = name; loc}))
     else
       match p.Parser.token with
       | Equal ->
@@ -2763,45 +2798,34 @@ and parse_jsx_prop p =
         let optional = Parser.optional p Question in
         Scanner.pop_mode p.scanner Jsx;
         let attr_expr = parse_primary_expr ~operand:(parse_atomic_expr p) p in
-        let label =
-          if optional then Asttypes.Optional {txt = name; loc}
-          else Asttypes.Labelled {txt = name; loc}
-        in
-        Some (label, attr_expr)
-      | _ ->
-        let attr_expr =
-          Ast_helper.Exp.ident ~loc (Location.mkloc (Longident.Lident name) loc)
-        in
-        let label =
-          if optional then Asttypes.Optional {txt = name; loc}
-          else Asttypes.Labelled {txt = name; loc}
-        in
-        Some (label, attr_expr))
+        Some (Parsetree.JSXPropValue ({txt = name; loc}, optional, attr_expr))
+      | _ -> Some (Parsetree.JSXPropPunning (false, {txt = name; loc})))
   (* {...props} *)
   | Lbrace -> (
     Scanner.pop_mode p.scanner Jsx;
+    let spread_start = p.Parser.start_pos in
     Parser.next p;
     match p.Parser.token with
     | DotDotDot -> (
       Scanner.pop_mode p.scanner Jsx;
       Parser.next p;
-      let loc = mk_loc p.Parser.start_pos p.prev_end_pos in
       let attr_expr = parse_primary_expr ~operand:(parse_expr p) p in
-      (* using label "spreadProps" to distinguish from others *)
-      let label = Asttypes.Labelled {txt = "_spreadProps"; loc} in
       match p.Parser.token with
       | Rbrace ->
+        let spread_end = p.Parser.end_pos in
+        let loc = mk_loc spread_start spread_end in
         Parser.next p;
         Scanner.set_jsx_mode p.scanner;
-        Some (label, attr_expr)
+        Some (Parsetree.JSXPropSpreading (loc, attr_expr))
+        (* Some (label, attr_expr) *)
       | _ -> None)
     | _ -> None)
   | _ -> None
 
-and parse_jsx_props p =
+and parse_jsx_props p : Parsetree.jsx_prop list =
   parse_region ~grammar:Grammar.JsxAttribute ~f:parse_jsx_prop p
 
-and parse_jsx_children p =
+and parse_jsx_children p : Parsetree.jsx_children =
   Scanner.pop_mode p.scanner Jsx;
   let rec loop p children =
     match p.Parser.token with
@@ -2829,17 +2853,20 @@ and parse_jsx_children p =
       loop p (child :: children)
     | _ -> children
   in
-  let spread, children =
+  let children =
     match p.Parser.token with
     | DotDotDot ->
       Parser.next p;
-      (true, [parse_primary_expr ~operand:(parse_atomic_expr p) ~no_call:true p])
+      let expr =
+        parse_primary_expr ~operand:(parse_atomic_expr p) ~no_call:true p
+      in
+      Parsetree.JSXChildrenSpreading expr
     | _ ->
       let children = List.rev (loop p []) in
-      (false, children)
+      Parsetree.JSXChildrenItems children
   in
   Scanner.set_jsx_mode p.scanner;
-  (spread, children)
+  children
 
 and parse_braced_or_record_expr p =
   let start_pos = p.Parser.start_pos in
@@ -3871,9 +3898,10 @@ and parse_list_expr ~start_pos p =
   in
   let make_sub_expr = function
     | exprs, Some spread, start_pos, end_pos ->
-      make_list_expression (mk_loc start_pos end_pos) exprs (Some spread)
+      Ast_helper.Exp.make_list_expression (mk_loc start_pos end_pos) exprs
+        (Some spread)
     | exprs, None, start_pos, end_pos ->
-      make_list_expression (mk_loc start_pos end_pos) exprs None
+      Ast_helper.Exp.make_list_expression (mk_loc start_pos end_pos) exprs None
   in
   let list_exprs_rev =
     parse_comma_delimited_reversed_list p ~grammar:Grammar.ListExpr
@@ -3882,9 +3910,10 @@ and parse_list_expr ~start_pos p =
   Parser.expect Rbrace p;
   let loc = mk_loc start_pos p.prev_end_pos in
   match split_by_spread list_exprs_rev with
-  | [] -> make_list_expression loc [] None
-  | [(exprs, Some spread, _, _)] -> make_list_expression loc exprs (Some spread)
-  | [(exprs, None, _, _)] -> make_list_expression loc exprs None
+  | [] -> Ast_helper.Exp.make_list_expression loc [] None
+  | [(exprs, Some spread, _, _)] ->
+    Ast_helper.Exp.make_list_expression loc exprs (Some spread)
+  | [(exprs, None, _, _)] -> Ast_helper.Exp.make_list_expression loc exprs None
   | exprs ->
     let list_exprs = List.map make_sub_expr exprs in
     Ast_helper.Exp.apply ~loc
diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml
index 78f938710e..94f39b0b4e 100644
--- a/compiler/syntax/src/res_parens.ml
+++ b/compiler/syntax/src/res_parens.ml
@@ -65,9 +65,8 @@ let structure_expr expr =
   | Some ({Location.loc = braces_loc}, _) -> Braced braces_loc
   | None -> (
     match expr with
-    | _
-      when ParsetreeViewer.has_attributes expr.pexp_attributes
-           && not (ParsetreeViewer.is_jsx_expression expr) ->
+    | {pexp_desc = Pexp_jsx_element _} -> Nothing
+    | _ when ParsetreeViewer.has_attributes expr.pexp_attributes ->
       Parenthesized
     | {
      Parsetree.pexp_desc =
@@ -376,7 +375,7 @@ let jsx_child_expr expr =
          ( Pexp_ident _ | Pexp_constant _ | Pexp_field _ | Pexp_construct _
          | Pexp_variant _ | Pexp_array _ | Pexp_pack _ | Pexp_record _
          | Pexp_extension _ | Pexp_letmodule _ | Pexp_letexception _
-         | Pexp_open _ | Pexp_sequence _ | Pexp_let _ );
+         | Pexp_open _ | Pexp_sequence _ | Pexp_let _ | Pexp_jsx_element _ );
        pexp_attributes = [];
       } ->
         Nothing
@@ -387,7 +386,7 @@ let jsx_child_expr expr =
        pexp_attributes = [];
       } ->
         Nothing
-      | expr when ParsetreeViewer.is_jsx_expression expr -> Nothing
+      | {pexp_desc = Pexp_jsx_element _} -> Nothing
       | _ -> Parenthesized))
 
 let binary_expr expr =
diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml
index 67993b4190..5305bd3fc2 100644
--- a/compiler/syntax/src/res_parsetree_viewer.ml
+++ b/compiler/syntax/src/res_parsetree_viewer.ml
@@ -493,26 +493,6 @@ let filter_fragile_match_attributes attrs =
       | _ -> true)
     attrs
 
-let is_jsx_expression expr =
-  let rec loop attrs =
-    match attrs with
-    | [] -> false
-    | ({Location.txt = "JSX"}, _) :: _ -> true
-    | _ :: attrs -> loop attrs
-  in
-  match expr.pexp_desc with
-  | Pexp_apply _ -> loop expr.Parsetree.pexp_attributes
-  | _ -> false
-
-let has_jsx_attribute attributes =
-  let rec loop attrs =
-    match attrs with
-    | [] -> false
-    | ({Location.txt = "JSX"}, _) :: _ -> true
-    | _ :: attrs -> loop attrs
-  in
-  loop attributes
-
 let should_indent_binary_expr expr =
   let same_precedence_sub_expression operator sub_expression =
     match sub_expression with
@@ -759,3 +739,29 @@ let is_tuple_array (expr : Parsetree.expression) =
   match expr with
   | {pexp_desc = Pexp_array items} -> List.for_all is_plain_tuple items
   | _ -> false
+
+let get_jsx_prop_loc = function
+  | Parsetree.JSXPropPunning (_, name) -> name.loc
+  | Parsetree.JSXPropValue (name, _, value) ->
+    {name.loc with loc_end = value.pexp_loc.loc_end}
+  | Parsetree.JSXPropSpreading (loc, _) -> loc
+
+let container_element_closing_tag_loc
+    (tag : Parsetree.jsx_closing_container_tag) =
+  {
+    tag.jsx_closing_container_tag_name.loc with
+    loc_start = tag.jsx_closing_container_tag_start;
+    loc_end = tag.jsx_closing_container_tag_end;
+  }
+
+(** returns the location of the /> token in a unary element *)
+let unary_element_closing_token (expression_loc : Warnings.loc) =
+  {
+    expression_loc with
+    loc_start =
+      {
+        expression_loc.loc_end with
+        pos_cnum = expression_loc.loc_end.pos_cnum - 2;
+        pos_bol = expression_loc.loc_end.pos_bol - 2;
+      };
+  }
diff --git a/compiler/syntax/src/res_parsetree_viewer.mli b/compiler/syntax/src/res_parsetree_viewer.mli
index 0cc0086dd1..c237a89e6f 100644
--- a/compiler/syntax/src/res_parsetree_viewer.mli
+++ b/compiler/syntax/src/res_parsetree_viewer.mli
@@ -91,9 +91,6 @@ val filter_ternary_attributes : Parsetree.attributes -> Parsetree.attributes
 val filter_fragile_match_attributes :
   Parsetree.attributes -> Parsetree.attributes
 
-val is_jsx_expression : Parsetree.expression -> bool
-val has_jsx_attribute : Parsetree.attributes -> bool
-
 val should_indent_binary_expr : Parsetree.expression -> bool
 val should_inline_rhs_binary_expr : Parsetree.expression -> bool
 val has_printable_attributes : Parsetree.attributes -> bool
@@ -159,3 +156,10 @@ val is_rewritten_underscore_apply_sugar : Parsetree.expression -> bool
 val is_fun_newtype : Parsetree.expression -> bool
 
 val is_tuple_array : Parsetree.expression -> bool
+
+val get_jsx_prop_loc : Parsetree.jsx_prop -> Warnings.loc
+
+val container_element_closing_tag_loc :
+  Parsetree.jsx_closing_container_tag -> Warnings.loc
+
+val unary_element_closing_token : Warnings.loc -> Warnings.loc
diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml
index fd5cee7480..5b2a176068 100644
--- a/compiler/syntax/src/res_printer.ml
+++ b/compiler/syntax/src/res_printer.ml
@@ -50,6 +50,17 @@ let has_leading_line_comment tbl loc =
   | Some comment -> Comment.is_single_line_comment comment
   | None -> false
 
+let get_leading_line_comment_count tbl loc =
+  match Hashtbl.find_opt tbl.CommentTable.leading loc with
+  | Some comments ->
+    List.filter Comment.is_single_line_comment comments |> List.length
+  | None -> 0
+
+let has_trailing_single_line_comment tbl loc =
+  match Hashtbl.find_opt tbl.CommentTable.trailing loc with
+  | Some (comment :: _) -> Comment.is_single_line_comment comment
+  | _ -> false
+
 let has_comment_below tbl loc =
   match Hashtbl.find tbl.CommentTable.trailing loc with
   | comment :: _ ->
@@ -58,18 +69,6 @@ let has_comment_below tbl loc =
   | [] -> false
   | exception Not_found -> false
 
-let has_nested_jsx_or_more_than_one_child expr =
-  let rec loop in_recursion expr =
-    match expr.Parsetree.pexp_desc with
-    | Pexp_construct
-        ({txt = Longident.Lident "::"}, Some {pexp_desc = Pexp_tuple [hd; tail]})
-      ->
-      if in_recursion || ParsetreeViewer.is_jsx_expression hd then true
-      else loop true tail
-    | _ -> false
-  in
-  loop false expr
-
 let has_comments_inside tbl loc =
   match Hashtbl.find_opt tbl.CommentTable.inside loc with
   | None -> false
@@ -80,6 +79,11 @@ let has_trailing_comments tbl loc =
   | None -> false
   | _ -> true
 
+let has_leading_comments tbl loc =
+  match Hashtbl.find_opt tbl.CommentTable.leading loc with
+  | None -> false
+  | _ -> true
+
 let print_multiline_comment_content txt =
   (* Turns
    *         |* first line
@@ -2146,6 +2150,7 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl
           | {pexp_desc = Pexp_newtype _} -> false
           | {pexp_attributes = [({Location.txt = "res.taggedTemplate"}, _)]} ->
             false
+          | {pexp_desc = Pexp_jsx_element _} -> true
           | e ->
             ParsetreeViewer.has_attributes e.pexp_attributes
             || ParsetreeViewer.is_array_access e)
@@ -2772,7 +2777,8 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl =
       let should_indent =
         match return_expr.pexp_desc with
         | Pexp_sequence _ | Pexp_let _ | Pexp_letmodule _ | Pexp_letexception _
-        | Pexp_open _ ->
+        | Pexp_open _
+        | Pexp_jsx_element (Jsx_fragment _) ->
           false
         | _ -> true
       in
@@ -2826,9 +2832,32 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl =
     | Pexp_fun _ | Pexp_newtype _ -> print_arrow e
     | Parsetree.Pexp_constant c ->
       print_constant ~template_literal:(ParsetreeViewer.is_template_literal e) c
-    | Pexp_construct _ when ParsetreeViewer.has_jsx_attribute e.pexp_attributes
-      ->
-      print_jsx_fragment ~state e cmt_tbl
+    | Pexp_jsx_element
+        (Jsx_fragment
+           {
+             jsx_fragment_opening = o;
+             jsx_fragment_children = children;
+             jsx_fragment_closing = c;
+           }) ->
+      print_jsx_fragment ~state o children c e.pexp_loc cmt_tbl
+    | Pexp_jsx_element
+        (Jsx_unary_element
+           {
+             jsx_unary_element_tag_name = tag_name;
+             jsx_unary_element_props = props;
+           }) ->
+      print_jsx_unary_tag ~state tag_name props e.pexp_loc cmt_tbl
+    | Pexp_jsx_element
+        (Jsx_container_element
+           {
+             jsx_container_element_tag_name_start = tag_name;
+             jsx_container_element_opening_tag_end = opening_greater_than;
+             jsx_container_element_props = props;
+             jsx_container_element_children = children;
+             jsx_container_element_closing_tag = closing_tag;
+           }) ->
+      print_jsx_container_tag ~state tag_name opening_greater_than props
+        children closing_tag e.pexp_loc cmt_tbl
     | Pexp_construct ({txt = Longident.Lident "()"}, _) -> Doc.text "()"
     | Pexp_construct ({txt = Longident.Lident "[]"}, _) ->
       Doc.concat
@@ -3457,9 +3486,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl =
     | Pexp_ifthenelse _ ->
       true
     | Pexp_match _ when ParsetreeViewer.is_if_let_expr e -> true
-    | Pexp_construct _ when ParsetreeViewer.has_jsx_attribute e.pexp_attributes
-      ->
-      true
+    | Pexp_jsx_element _ -> true
     | _ -> false
   in
   match e.pexp_attributes with
@@ -4278,10 +4305,6 @@ and print_pexp_apply ~state expr cmt_tbl =
               Doc.indent (Doc.concat [Doc.line; target_expr])
             else Doc.concat [Doc.space; target_expr]);
          ])
-  (* TODO: cleanup, are those branches even remotely performant? *)
-  | Pexp_apply {funct = {pexp_desc = Pexp_ident lident}; args}
-    when ParsetreeViewer.is_jsx_expression expr ->
-    print_jsx_expression ~state lident args cmt_tbl
   | Pexp_apply {funct = call_expr; args; partial} ->
     let args =
       List.map
@@ -4345,55 +4368,114 @@ and print_pexp_apply ~state expr cmt_tbl =
         [print_attributes ~state attrs cmt_tbl; call_expr_doc; args_doc]
   | _ -> assert false
 
-and print_jsx_expression ~state lident args cmt_tbl =
-  let name = print_jsx_name lident in
-  let formatted_props, children = print_jsx_props ~state args cmt_tbl in
-  (* <div className="test" /> *)
-  let has_children =
-    match children with
-    | Some
-        {
-          Parsetree.pexp_desc =
-            Pexp_construct ({txt = Longident.Lident "[]"}, None);
-        } ->
-      false
+and print_jsx_unary_tag ~state tag_name props expr_loc cmt_tbl =
+  let name = print_jsx_name tag_name in
+  let formatted_props = print_jsx_props ~state props cmt_tbl in
+  let tag_has_trailing_comment = has_trailing_comments cmt_tbl tag_name.loc in
+  let tag_has_no_props = List.length props == 0 in
+  let closing_token_loc =
+    ParsetreeViewer.unary_element_closing_token expr_loc
+  in
+  let props_doc =
+    if tag_has_no_props then
+      if has_leading_comments cmt_tbl closing_token_loc then Doc.soft_line
+      else if tag_has_trailing_comment then Doc.nil
+      else Doc.space
+    else
+      Doc.concat
+        [
+          Doc.indent
+            (Doc.concat
+               [Doc.line; Doc.group (Doc.join ~sep:Doc.line formatted_props)]);
+          Doc.line;
+        ]
+  in
+  let opening_tag =
+    print_comments
+      (Doc.concat [Doc.less_than; name])
+      cmt_tbl tag_name.Asttypes.loc
+  in
+  let opening_tag_doc =
+    if tag_has_trailing_comment && not tag_has_no_props then
+      Doc.indent opening_tag
+    else opening_tag
+  in
+  let closing_tag_doc =
+    print_comments (Doc.text "/>") cmt_tbl closing_token_loc
+  in
+  Doc.group
+    (Doc.concat
+       [
+         opening_tag_doc;
+         props_doc;
+         (if tag_has_trailing_comment && tag_has_no_props then Doc.space
+          else Doc.nil);
+         closing_tag_doc;
+       ])
+
+and print_jsx_container_tag ~state tag_name
+    (opening_greater_than : Lexing.position) props
+    (children : Parsetree.jsx_children)
+    (closing_tag : Parsetree.jsx_closing_container_tag option)
+    (pexp_loc : Location.t) cmt_tbl =
+  let name = print_jsx_name tag_name in
+  let last_prop_has_comment_after =
+    let rec visit props =
+      match props with
+      | [] -> None
+      | [x] -> Some x
+      | _ :: xs -> visit xs
+    in
+    let last_prop = visit props in
+    match last_prop with
     | None -> false
-    | _ -> true
+    | Some last_prop ->
+      has_trailing_comments cmt_tbl (ParsetreeViewer.get_jsx_prop_loc last_prop)
+  in
+  let opening_greater_than_loc =
+    {
+      Warnings.loc_start = opening_greater_than;
+      loc_end = opening_greater_than;
+      loc_ghost = false;
+    }
+  in
+  let opening_greater_than_has_leading_comments, opening_greater_than_doc =
+    let has_leading_comments =
+      has_leading_comments cmt_tbl opening_greater_than_loc
+    in
+    ( has_leading_comments,
+      print_comments Doc.greater_than cmt_tbl opening_greater_than_loc )
   in
-  let is_self_closing =
+  let formatted_props = print_jsx_props ~state props cmt_tbl in
+  (* <div className="test" /> *)
+  let has_children =
     match children with
-    | Some
-        {
-          Parsetree.pexp_desc =
-            Pexp_construct ({txt = Longident.Lident "[]"}, None);
-          pexp_loc = loc;
-        } ->
-      not (has_comments_inside cmt_tbl loc)
-    | _ -> false
+    | JSXChildrenSpreading _ | JSXChildrenItems (_ :: _) -> true
+    | JSXChildrenItems [] -> false
   in
+  let line_sep = get_line_sep_for_jsx_children children in
   let print_children children =
-    let line_sep =
-      match children with
-      | Some expr ->
-        if has_nested_jsx_or_more_than_one_child expr then Doc.hard_line
-        else Doc.line
-      | None -> Doc.line
-    in
     Doc.concat
       [
         Doc.indent
-          (Doc.concat
-             [
-               Doc.line;
-               (match children with
-               | Some children_expression ->
-                 print_jsx_children ~state children_expression ~sep:line_sep
-                   cmt_tbl
-               | None -> Doc.nil);
-             ]);
+          (Doc.concat [Doc.line; print_jsx_children ~state children cmt_tbl]);
         line_sep;
       ]
   in
+
+  (* comments between the opening and closing tag *)
+  let has_comments_inside = has_comments_inside cmt_tbl pexp_loc in
+  let closing_element_doc =
+    match closing_tag with
+    | None -> Doc.nil
+    | Some closing_tag ->
+      let closing_tag_loc =
+        ParsetreeViewer.container_element_closing_tag_loc closing_tag
+      in
+      print_comments
+        (Doc.concat [Doc.text "</"; name; Doc.greater_than])
+        cmt_tbl closing_tag_loc
+  in
   Doc.group
     (Doc.concat
        [
@@ -4402,282 +4484,240 @@ and print_jsx_expression ~state lident args cmt_tbl =
               [
                 print_comments
                   (Doc.concat [Doc.less_than; name])
-                  cmt_tbl lident.Asttypes.loc;
-                formatted_props;
-                (match children with
-                | Some
-                    {
-                      Parsetree.pexp_desc =
-                        Pexp_construct ({txt = Longident.Lident "[]"}, None);
-                    }
-                  when is_self_closing ->
-                  Doc.text "/>"
-                | _ ->
-                  (* if tag A has trailing comments then put > on the next line
-                     <A
-                     // comments
-                     >
-                     </A>
-                  *)
-                  if has_trailing_comments cmt_tbl lident.Asttypes.loc then
-                    Doc.concat [Doc.soft_line; Doc.greater_than]
-                  else Doc.greater_than);
-              ]);
-         (if is_self_closing then Doc.nil
-          else
-            Doc.concat
-              [
-                (if has_children then print_children children
+                  cmt_tbl tag_name.Asttypes.loc;
+                (if List.length formatted_props == 0 then Doc.nil
                  else
-                   match children with
-                   | Some
-                       {
-                         Parsetree.pexp_desc =
-                           Pexp_construct ({txt = Longident.Lident "[]"}, None);
-                         pexp_loc = loc;
-                       } ->
-                     print_comments_inside cmt_tbl loc
-                   | _ -> Doc.nil);
-                Doc.text "</";
-                name;
-                Doc.greater_than;
+                   Doc.indent
+                     (Doc.concat
+                        [
+                          Doc.line;
+                          Doc.group (Doc.join formatted_props ~sep:Doc.line);
+                        ]));
+                (* 
+                if the element name has a single comment on the same line
+
+                <A // foo
+                >
+                </A>
+
+                We need to force a newline.
+               *)
+                (if
+                   has_trailing_single_line_comment cmt_tbl
+                     tag_name.Asttypes.loc
+                 then Doc.concat [Doc.hard_line; opening_greater_than_doc]
+                   (*
+                  if the last prop has trailing comment
+
+                  <A
+                    prop=value
+                    // comments
+                  >
+                  </A>
+
+                  or there are leading comments before `>`
+
+                  <A
+                    // comments
+                  >
+
+                  then put > on the next line
+                 *)
+                 else if
+                   last_prop_has_comment_after
+                   || opening_greater_than_has_leading_comments
+                 then Doc.concat [Doc.soft_line; opening_greater_than_doc]
+                 else opening_greater_than_doc);
               ]);
+         Doc.concat
+           [
+             (if has_children then print_children children
+              else if not has_comments_inside then Doc.soft_line
+              else print_comments_inside cmt_tbl pexp_loc);
+             closing_element_doc;
+           ];
        ])
 
-and print_jsx_fragment ~state expr cmt_tbl =
-  let opening = Doc.text "<>" in
-  let closing = Doc.text "</>" in
-  let line_sep =
-    if has_nested_jsx_or_more_than_one_child expr then Doc.hard_line
-    else Doc.line
+and print_jsx_fragment ~state (opening_greater_than : Lexing.position)
+    (children : Parsetree.jsx_children) (closing_lesser_than : Lexing.position)
+    (fragment_loc : Warnings.loc) cmt_tbl =
+  let opening =
+    let loc : Location.t = {fragment_loc with loc_end = opening_greater_than} in
+    print_comments (Doc.text "<>") cmt_tbl loc
+  in
+  let closing =
+    let loc : Location.t =
+      {fragment_loc with loc_start = closing_lesser_than}
+    in
+    print_comments (Doc.text "</>") cmt_tbl loc
   in
+  let has_children =
+    match children with
+    | JSXChildrenItems [] -> false
+    | JSXChildrenSpreading _ | JSXChildrenItems (_ :: _) -> true
+  in
+  let line_sep = get_line_sep_for_jsx_children children in
   Doc.group
     (Doc.concat
        [
          opening;
-         (match expr.pexp_desc with
-         | Pexp_construct ({txt = Longident.Lident "[]"}, None) -> Doc.nil
-         | _ ->
-           Doc.indent
-             (Doc.concat
-                [Doc.line; print_jsx_children ~state expr ~sep:line_sep cmt_tbl]));
-         line_sep;
+         Doc.indent
+           (Doc.concat [Doc.line; print_jsx_children ~state children cmt_tbl]);
+         (if has_children then line_sep else Doc.nil);
          closing;
        ])
 
-and print_jsx_children ~state (children_expr : Parsetree.expression) ~sep
-    cmt_tbl =
-  match children_expr.pexp_desc with
-  | Pexp_construct ({txt = Longident.Lident "::"}, _) ->
-    let children, _ = ParsetreeViewer.collect_list_expressions children_expr in
-    let print_expr (expr : Parsetree.expression) =
-      let leading_line_comment_present =
-        has_leading_line_comment cmt_tbl expr.pexp_loc
-      in
-      let expr_doc = print_expression_with_comments ~state expr cmt_tbl in
-      let add_parens_or_braces expr_doc =
-        (* {(20: int)} make sure that we also protect the expression inside *)
-        let inner_doc =
-          if Parens.braced_expr expr then add_parens expr_doc else expr_doc
-        in
-        if leading_line_comment_present then add_braces inner_doc
-        else Doc.concat [Doc.lbrace; inner_doc; Doc.rbrace]
-      in
-      match Parens.jsx_child_expr expr with
-      | Nothing -> expr_doc
-      | Parenthesized -> add_parens_or_braces expr_doc
-      | Braced braces_loc ->
-        print_comments (add_parens_or_braces expr_doc) cmt_tbl braces_loc
+and get_line_sep_for_jsx_children (children : Parsetree.jsx_children) =
+  match children with
+  | JSXChildrenSpreading _ -> Doc.line
+  | JSXChildrenItems children ->
+    if
+      List.length children > 1
+      || List.exists
+           (function
+             | {Parsetree.pexp_desc = Pexp_jsx_element _} -> true
+             | _ -> false)
+           children
+    then Doc.hard_line
+    else Doc.line
+
+and print_jsx_children ~state (children : Parsetree.jsx_children) cmt_tbl =
+  let open Parsetree in
+  let sep = get_line_sep_for_jsx_children children in
+  let print_expr (expr : Parsetree.expression) =
+    let leading_line_comment_present =
+      has_leading_line_comment cmt_tbl expr.pexp_loc
     in
-    let get_first_leading_comment loc =
-      match get_first_leading_comment cmt_tbl loc with
-      | None -> loc
-      | Some comment -> Comment.loc comment
+    let expr_doc = print_expression_with_comments ~state expr cmt_tbl in
+    let add_parens_or_braces expr_doc =
+      (* {(20: int)} make sure that we also protect the expression inside *)
+      let inner_doc =
+        if Parens.braced_expr expr then add_parens expr_doc else expr_doc
+      in
+      if leading_line_comment_present then add_braces inner_doc
+      else Doc.concat [Doc.lbrace; inner_doc; Doc.rbrace]
     in
-    let get_loc expr =
-      match ParsetreeViewer.process_braces_attr expr with
-      | None, _ -> get_first_leading_comment expr.pexp_loc
-      | Some ({loc}, _), _ -> get_first_leading_comment loc
+    match Parens.jsx_child_expr expr with
+    | Nothing -> expr_doc
+    | Parenthesized -> add_parens_or_braces expr_doc
+    | Braced braces_loc ->
+      print_comments (add_parens_or_braces expr_doc) cmt_tbl braces_loc
+  in
+  match children with
+  | JSXChildrenItems [] -> Doc.nil
+  | JSXChildrenSpreading child -> Doc.concat [Doc.dotdotdot; print_expr child]
+  | JSXChildrenItems children ->
+    let get_loc (expr : Parsetree.expression) =
+      let braces =
+        expr.pexp_attributes
+        |> List.find_map (fun (attr, _) ->
+               match attr with
+               | {Location.txt = "res.braces"; loc} -> Some loc
+               | _ -> None)
+      in
+      match braces with
+      | None -> expr.pexp_loc
+      | Some loc -> loc
     in
-    let rec loop prev acc exprs =
-      match exprs with
-      | [] -> List.rev acc
-      | expr :: tails ->
-        let start_loc = (get_loc expr).loc_start.pos_lnum in
-        let end_loc = (get_loc prev).loc_end.pos_lnum in
-        let expr_doc = print_expr expr in
-        let docs =
-          if start_loc - end_loc > 1 then
-            Doc.concat [Doc.hard_line; expr_doc] :: acc
-          else expr_doc :: acc
+
+    let rec visit acc children =
+      match children with
+      | [] -> acc
+      | [x] -> Doc.concat [acc; print_expr x]
+      | x :: (y :: _ as rest) ->
+        let end_line_x =
+          let loc = get_loc x in
+          loc.loc_end.pos_lnum
         in
-        loop expr docs tails
-    in
-    let docs = loop children_expr [] children in
-    Doc.group (Doc.join ~sep docs)
-  | _ ->
-    let leading_line_comment_present =
-      has_leading_line_comment cmt_tbl children_expr.pexp_loc
-    in
-    let expr_doc =
-      print_expression_with_comments ~state children_expr cmt_tbl
+        let start_line_y =
+          let loc = get_loc y in
+          loc.loc_start.pos_lnum
+        in
+        let lines_between = start_line_y - end_line_x - 1 in
+        let leading_single_line_comments =
+          get_leading_line_comment_count cmt_tbl (get_loc y)
+        in
+        (* If there are lines between the jsx elements, we preserve at least one line *)
+        if
+          (* Unless they are all comments *)
+          (* The edge case of comment followed by blank line is not caught here *)
+          lines_between > 0 && not (lines_between = leading_single_line_comments)
+        then
+          let doc = Doc.concat [print_expr x; sep; Doc.hard_line] in
+          visit (Doc.concat [acc; doc]) rest
+        else
+          let doc = Doc.concat [print_expr x; sep] in
+          visit (Doc.concat [acc; doc]) rest
     in
-    Doc.concat
-      [
-        Doc.dotdotdot;
-        (match Parens.jsx_child_expr children_expr with
+    visit Doc.nil children
+
+and print_jsx_prop ~state prop cmt_tbl =
+  let open Parsetree in
+  let prop_loc = ParsetreeViewer.get_jsx_prop_loc prop in
+  let doc =
+    match prop with
+    | JSXPropPunning (is_optional, name) ->
+      (* We don't print any comments here because they will be attached to the entire prop_loc *)
+      if is_optional then Doc.concat [Doc.question; print_ident_like name.txt]
+      else print_ident_like name.txt
+    | JSXPropValue (name, is_optional, value) ->
+      let has_trailing_comment_after_name =
+        has_trailing_single_line_comment cmt_tbl name.loc
+      in
+      let value_doc =
+        let leading_line_comment_present =
+          (* If the value expression has braces, these will be representend as an attribute containing the brace range *)
+          (* comment assignment is a little weird that this point, it will be assigned to a child node of the value expression *)
+          match (Parens.jsx_prop_expr value, value.pexp_desc) with
+          | ( Braced _,
+              Parsetree.Pexp_apply {funct = fun_expr; args = (_, head_arg) :: _}
+            ) ->
+            has_leading_line_comment cmt_tbl fun_expr.pexp_loc
+            || has_leading_line_comment cmt_tbl head_arg.pexp_loc
+          | _ -> has_leading_line_comment cmt_tbl value.pexp_loc
+        in
+        let doc = print_expression_with_comments ~state value cmt_tbl in
+        match Parens.jsx_prop_expr value with
         | Parenthesized | Braced _ ->
+          (* {(20: int)} make sure that we also protect the expression inside *)
           let inner_doc =
-            if Parens.braced_expr children_expr then add_parens expr_doc
-            else expr_doc
+            if Parens.braced_expr value then add_parens doc else doc
           in
           if leading_line_comment_present then add_braces inner_doc
           else Doc.concat [Doc.lbrace; inner_doc; Doc.rbrace]
-        | Nothing -> expr_doc);
-      ]
-
-and print_jsx_props ~state args cmt_tbl : Doc.t * Parsetree.expression option =
-  (* This function was introduced because we have different formatting behavior for self-closing tags and other tags
-     we always put /> on a new line for self-closing tag when it breaks
-     <A
-      a=""
-     />
-
-     <A
-     a="">
-      <B />
-     </A>
-     we should remove this function once the format is unified
-  *)
-  let is_self_closing children =
-    match children with
-    | {
-     Parsetree.pexp_desc = Pexp_construct ({txt = Longident.Lident "[]"}, None);
-     pexp_loc = loc;
-    } ->
-      not (has_comments_inside cmt_tbl loc)
-    | _ -> false
-  in
-  let rec loop props args =
-    match args with
-    | [] -> (Doc.nil, None)
-    | [
-     (Asttypes.Labelled {txt = "children"}, children);
-     ( Asttypes.Nolabel,
-       {
-         Parsetree.pexp_desc =
-           Pexp_construct ({txt = Longident.Lident "()"}, None);
-       } );
-    ] ->
-      let doc = if is_self_closing children then Doc.line else Doc.nil in
-      (doc, Some children)
-    | ((e_lbl, expr) as last_prop)
-      :: [
-           (Asttypes.Labelled {txt = "children"}, children);
-           ( Asttypes.Nolabel,
-             {
-               Parsetree.pexp_desc =
-                 Pexp_construct ({txt = Longident.Lident "()"}, None);
-             } );
-         ] ->
-      let loc =
-        match e_lbl with
-        | Asttypes.Labelled {loc} | Asttypes.Optional {loc} ->
-          {loc with loc_end = expr.pexp_loc.loc_end}
-        | Nolabel -> expr.pexp_loc
+        | _ -> doc
       in
-      let trailing_comments_present = has_trailing_comments cmt_tbl loc in
-      let prop_doc = print_jsx_prop ~state last_prop cmt_tbl in
-      let formatted_props =
+      let doc =
         Doc.concat
           [
-            Doc.indent
-              (Doc.concat
-                 [
-                   Doc.line;
-                   Doc.group
-                     (Doc.join ~sep:Doc.line (prop_doc :: props |> List.rev));
-                 ]);
-            (* print > on new line if the last prop has trailing comments *)
-            (match (is_self_closing children, trailing_comments_present) with
-            (* we always put /> on a new line when a self-closing tag breaks *)
-            | true, _ -> Doc.line
-            | false, true -> Doc.soft_line
-            | false, false -> Doc.nil);
+            print_comments (print_ident_like name.txt) cmt_tbl name.loc;
+            Doc.equal;
+            (if is_optional then Doc.question else Doc.nil);
+            (if has_trailing_comment_after_name then Doc.hard_line else Doc.nil);
+            Doc.group value_doc;
           ]
       in
-      (formatted_props, Some children)
-    | arg :: args ->
-      let prop_doc = print_jsx_prop ~state arg cmt_tbl in
-      loop (prop_doc :: props) args
+      print_comments doc cmt_tbl value.pexp_loc
+    | JSXPropSpreading (_, value) ->
+      Doc.group
+        (Doc.concat
+           [
+             Doc.lbrace;
+             Doc.dotdotdot;
+             print_expression_with_comments ~state value cmt_tbl;
+             Doc.rbrace;
+           ])
   in
-  loop [] args
+  print_comments doc cmt_tbl prop_loc
 
-and print_jsx_prop ~state arg cmt_tbl =
-  match arg with
-  | ( ((Asttypes.Labelled {txt = lbl_txt} | Optional {txt = lbl_txt}) as lbl),
-      {
-        pexp_attributes = [];
-        pexp_desc = Pexp_ident {txt = Longident.Lident ident};
-      } )
-    when lbl_txt = ident (* jsx punning *) -> (
-    match lbl with
-    | Nolabel -> Doc.nil
-    | Labelled {loc} -> print_comments (print_ident_like ident) cmt_tbl loc
-    | Optional {loc} ->
-      let doc = Doc.concat [Doc.question; print_ident_like ident] in
-      print_comments doc cmt_tbl loc)
-  | ( ((Asttypes.Labelled {txt = lbl_txt} | Optional {txt = lbl_txt}) as lbl),
-      {
-        Parsetree.pexp_attributes = [];
-        pexp_desc = Pexp_ident {txt = Longident.Lident ident};
-      } )
-    when lbl_txt = ident (* jsx punning when printing from Reason *) -> (
-    match lbl with
-    | Nolabel -> Doc.nil
-    | Labelled _lbl -> print_ident_like ident
-    | Optional _lbl -> Doc.concat [Doc.question; print_ident_like ident])
-  | Asttypes.Labelled {txt = "_spreadProps"}, expr ->
-    let doc = print_expression_with_comments ~state expr cmt_tbl in
-    Doc.concat [Doc.lbrace; Doc.dotdotdot; doc; Doc.rbrace]
-  | lbl, expr ->
-    let arg_loc, lbl_doc =
-      match lbl with
-      | Asttypes.Labelled {txt = lbl; loc} ->
-        let lbl = print_comments (print_ident_like lbl) cmt_tbl loc in
-        (loc, Doc.concat [lbl; Doc.equal])
-      | Asttypes.Optional {txt = lbl; loc} ->
-        let lbl = print_comments (print_ident_like lbl) cmt_tbl loc in
-        (loc, Doc.concat [lbl; Doc.equal; Doc.question])
-      | Nolabel -> (Location.none, Doc.nil)
-    in
-    let expr_doc =
-      let leading_line_comment_present =
-        has_leading_line_comment cmt_tbl expr.pexp_loc
-      in
-      let doc = print_expression_with_comments ~state expr cmt_tbl in
-      match Parens.jsx_prop_expr expr with
-      | Parenthesized | Braced _ ->
-        (* {(20: int)} make sure that we also protect the expression inside *)
-        let inner_doc =
-          if Parens.braced_expr expr then add_parens doc else doc
-        in
-        if leading_line_comment_present then add_braces inner_doc
-        else Doc.concat [Doc.lbrace; inner_doc; Doc.rbrace]
-      | _ -> doc
-    in
-    let full_loc = {arg_loc with loc_end = expr.pexp_loc.loc_end} in
-    print_comments (Doc.concat [lbl_doc; expr_doc]) cmt_tbl full_loc
+and print_jsx_props ~state props cmt_tbl : Doc.t list =
+  props |> List.map (fun prop -> print_jsx_prop ~state prop cmt_tbl)
 
-(* div -> div.
- * Navabar.createElement -> Navbar
- * Staff.Users.createElement -> Staff.Users *)
 and print_jsx_name {txt = lident} =
   let print_ident = print_ident_like ~allow_uident:true ~allow_hyphen:true in
   let rec flatten acc lident =
     match lident with
     | Longident.Lident txt -> print_ident txt :: acc
-    | Ldot (lident, "createElement") -> flatten acc lident
     | Ldot (lident, txt) -> flatten (print_ident txt :: acc) lident
     | _ -> acc
   in
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt b/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt
index 05f269144a..52d87f41f8 100644
--- a/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt
+++ b/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt
@@ -1,5 +1,5 @@
 Complete src/GenericJsxCompletion.res 0:8
-posCursor:[0:8] posNoWhite:[0:6] Found expr:[0:4->0:7]
+posCursor:[0:8] posNoWhite:[0:6] Found expr:[0:3->0:7]
 JSX <div:[0:4->0:7] > _children:None
 Completable: Cjsx([div], "", [])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -26,7 +26,7 @@ Path GenericJsx.Elements.props
   }]
 
 Complete src/GenericJsxCompletion.res 3:17
-posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:4->3:18]
+posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:3->3:18]
 JSX <div:[3:4->3:7] testing[3:8->3:15]=...[3:16->3:18]> _children:None
 Completable: Cexpression CJsxPropValue [div] testing->recordBody
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -93,14 +93,8 @@ posCursor:[20:24] posNoWhite:[20:23] Found expr:[11:4->22:10]
 posCursor:[20:24] posNoWhite:[20:23] Found expr:[12:4->22:10]
 posCursor:[20:24] posNoWhite:[20:23] Found expr:[13:4->22:10]
 posCursor:[20:24] posNoWhite:[20:23] Found expr:[16:4->22:10]
-posCursor:[20:24] posNoWhite:[20:23] Found expr:[17:5->22:10]
-JSX <div:[17:5->17:8] > _children:17:8
-posCursor:[20:24] posNoWhite:[20:23] Found expr:[17:8->22:4]
-posCursor:[20:24] posNoWhite:[20:23] Found expr:[18:7->22:4]
-posCursor:[20:24] posNoWhite:[20:23] Found expr:[19:7->22:4]
-posCursor:[20:24] posNoWhite:[20:23] Found expr:[19:7->22:4]
-posCursor:[20:24] posNoWhite:[20:23] Found expr:[20:10->22:4]
-posCursor:[20:24] posNoWhite:[20:23] Found expr:[20:10->22:4]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[17:4->22:10]
+JSX <div:[17:5->17:8] > _children:18:7
 posCursor:[20:24] posNoWhite:[20:23] Found expr:[20:10->20:24]
 Completable: Cpath Value[someString]->st <<jsx>>
 Raw opens: 1 GenericJsx.place holder
diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt
index 1b3f68e11c..5ae0ac9ca6 100644
--- a/tests/analysis_tests/tests/src/expected/Completion.res.txt
+++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt
@@ -711,7 +711,7 @@ XXX Not found!
 []
 
 Complete src/Completion.res 59:30
-posCursor:[59:30] posNoWhite:[59:29] Found expr:[59:15->59:30]
+posCursor:[59:30] posNoWhite:[59:29] Found expr:[59:14->59:30]
 JSX <O.Comp:[59:15->59:21] second[59:22->59:28]=...[59:29->59:30]> _children:None
 Completable: Cexpression CJsxPropValue [O, Comp] second=z
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -727,7 +727,7 @@ Path O.Comp.make
   }]
 
 Complete src/Completion.res 62:23
-posCursor:[62:23] posNoWhite:[62:22] Found expr:[62:15->62:23]
+posCursor:[62:23] posNoWhite:[62:22] Found expr:[62:14->62:23]
 JSX <O.Comp:[62:15->62:21] z[62:22->62:23]=...[62:22->62:23]> _children:None
 Completable: Cjsx([O, Comp], z, [z])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -975,7 +975,7 @@ Path Objects.Rec.
 
 Complete src/Completion.res 120:7
 posCursor:[120:7] posNoWhite:[120:6] Found expr:[119:11->123:1]
-posCursor:[120:7] posNoWhite:[120:6] Found expr:[120:5->122:5]
+posCursor:[120:7] posNoWhite:[120:6] Found expr:[120:5->122:8]
 posCursor:[120:7] posNoWhite:[120:6] Found expr:[120:5->120:7]
 Pexp_ident my:[120:5->120:7]
 Completable: Cpath Value[my]
@@ -1015,7 +1015,7 @@ Path Objects.object
   }]
 
 Complete src/Completion.res 151:6
-posCursor:[151:6] posNoWhite:[151:5] Found expr:[151:4->151:6]
+posCursor:[151:6] posNoWhite:[151:5] Found expr:[151:3->151:6]
 JSX <O.:[151:4->151:6] > _children:None
 Completable: Cpath Module[O, ""]
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -1121,7 +1121,7 @@ Path Lis
   }]
 
 Complete src/Completion.res 169:16
-posCursor:[169:16] posNoWhite:[169:15] Found expr:[169:4->169:16]
+posCursor:[169:16] posNoWhite:[169:15] Found expr:[169:3->169:16]
 JSX <WithChildren:[169:4->169:16] > _children:None
 Completable: Cpath Module[WithChildren]
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -1888,7 +1888,7 @@ Found type for function callback
   }]
 
 Complete src/Completion.res 339:26
-posCursor:[339:26] posNoWhite:[339:25] Found expr:[336:3->349:23]
+posCursor:[339:26] posNoWhite:[339:25] Found expr:[336:2->349:23]
 JSX <div:[336:3->336:6] onClick[337:4->337:11]=...[337:13->349:23]> _children:None
 posCursor:[339:26] posNoWhite:[339:25] Found expr:[337:13->349:23]
 posCursor:[339:26] posNoWhite:[339:25] Found expr:[337:13->341:6]
diff --git a/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt b/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt
index ae28106430..6c2529f079 100644
--- a/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt
+++ b/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt
@@ -424,8 +424,8 @@ Path fnTakingRecord
   }]
 
 Complete src/CompletionFunctionArguments.res 109:29
-posCursor:[109:29] posNoWhite:[109:28] Found expr:[105:3->114:4]
-JSX <div:[105:3->105:6] onMouseDown[106:4->106:15]=...[106:35->113:5]> _children:114:2
+posCursor:[109:29] posNoWhite:[109:28] Found expr:[105:2->114:4]
+JSX <div:[105:3->105:6] onMouseDown[106:4->106:15]=...[106:35->113:5]> _children:None
 posCursor:[109:29] posNoWhite:[109:28] Found expr:[106:35->113:5]
 posCursor:[109:29] posNoWhite:[109:28] Found expr:[107:6->109:29]
 posCursor:[109:29] posNoWhite:[109:28] Found expr:[108:6->109:29]
@@ -447,8 +447,8 @@ Path JsxEvent.Mouse.a
   }]
 
 Complete src/CompletionFunctionArguments.res 111:27
-posCursor:[111:27] posNoWhite:[111:26] Found expr:[105:3->114:4]
-JSX <div:[105:3->105:6] onMouseDown[106:4->106:15]=...[106:35->113:5]> _children:114:2
+posCursor:[111:27] posNoWhite:[111:26] Found expr:[105:2->114:4]
+JSX <div:[105:3->105:6] onMouseDown[106:4->106:15]=...[106:35->113:5]> _children:None
 posCursor:[111:27] posNoWhite:[111:26] Found expr:[106:35->113:5]
 posCursor:[111:27] posNoWhite:[111:26] Found expr:[107:6->111:27]
 posCursor:[111:27] posNoWhite:[111:26] Found expr:[108:6->111:27]
diff --git a/tests/analysis_tests/tests/src/expected/CompletionInferValues.res.txt b/tests/analysis_tests/tests/src/expected/CompletionInferValues.res.txt
index 8755e16516..afffa238ec 100644
--- a/tests/analysis_tests/tests/src/expected/CompletionInferValues.res.txt
+++ b/tests/analysis_tests/tests/src/expected/CompletionInferValues.res.txt
@@ -256,8 +256,8 @@ Path ReactEvent.Mouse.pr
   }]
 
 Complete src/CompletionInferValues.res 41:50
-posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:12->41:56]
-JSX <div:[41:12->41:15] onMouseEnter[41:16->41:28]=...[41:36->41:52]> _children:41:54
+posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:11->41:56]
+JSX <div:[41:12->41:15] onMouseEnter[41:16->41:28]=...[41:36->41:52]> _children:None
 posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:36->41:52]
 posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:41->41:50]
 Completable: Cpath Value[event]->pr <<jsx>>
@@ -281,8 +281,8 @@ Path JsxEvent.Mouse.pr
   }]
 
 Complete src/CompletionInferValues.res 44:50
-posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:12->44:56]
-JSX <Div:[44:12->44:15] onMouseEnter[44:16->44:28]=...[44:36->44:52]> _children:44:54
+posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:11->44:56]
+JSX <Div:[44:12->44:15] onMouseEnter[44:16->44:28]=...[44:36->44:52]> _children:None
 posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:36->44:52]
 posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:41->44:50]
 Completable: Cpath Value[event]->pr <<jsx>>
@@ -305,8 +305,8 @@ Path JsxEvent.Mouse.pr
   }]
 
 Complete src/CompletionInferValues.res 47:87
-posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:12->47:93]
-JSX <div:[47:12->47:15] onMouseEnter[47:16->47:28]=...[47:36->47:89]> _children:47:91
+posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:11->47:93]
+JSX <div:[47:12->47:15] onMouseEnter[47:16->47:28]=...[47:36->47:89]> _children:None
 posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:36->47:89]
 posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:41->47:87]
 posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:81->47:87]
@@ -383,8 +383,8 @@ Path Stdlib.Int.t
   }]
 
 Complete src/CompletionInferValues.res 50:103
-posCursor:[50:103] posNoWhite:[50:102] Found expr:[50:12->50:109]
-JSX <div:[50:12->50:15] onMouseEnter[50:16->50:28]=...[50:36->50:105]> _children:50:107
+posCursor:[50:103] posNoWhite:[50:102] Found expr:[50:11->50:109]
+JSX <div:[50:12->50:15] onMouseEnter[50:16->50:28]=...[50:36->50:105]> _children:None
 posCursor:[50:103] posNoWhite:[50:102] Found expr:[50:36->50:105]
 posCursor:[50:103] posNoWhite:[50:102] Found expr:[50:41->50:103]
 posCursor:[50:103] posNoWhite:[50:102] Found expr:[50:95->50:103]
@@ -400,8 +400,8 @@ Path Int.toString
 []
 
 Complete src/CompletionInferValues.res 53:121
-posCursor:[53:121] posNoWhite:[53:120] Found expr:[53:12->53:127]
-JSX <div:[53:12->53:15] onMouseEnter[53:16->53:28]=...[53:36->53:123]> _children:53:125
+posCursor:[53:121] posNoWhite:[53:120] Found expr:[53:11->53:127]
+JSX <div:[53:12->53:15] onMouseEnter[53:16->53:28]=...[53:36->53:123]> _children:None
 posCursor:[53:121] posNoWhite:[53:120] Found expr:[53:36->53:123]
 posCursor:[53:121] posNoWhite:[53:120] Found expr:[53:41->53:121]
 posCursor:[53:121] posNoWhite:[53:120] Found expr:[53:114->53:121]
diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt
index 29f2fd6b67..2375a24b36 100644
--- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt
+++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt
@@ -64,14 +64,8 @@ posCursor:[18:24] posNoWhite:[18:23] Found expr:[9:4->32:10]
 posCursor:[18:24] posNoWhite:[18:23] Found expr:[10:4->32:10]
 posCursor:[18:24] posNoWhite:[18:23] Found expr:[11:4->32:10]
 posCursor:[18:24] posNoWhite:[18:23] Found expr:[12:4->32:10]
-posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:5->32:10]
-JSX <div:[15:5->15:8] > _children:15:8
-posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:8->32:4]
-posCursor:[18:24] posNoWhite:[18:23] Found expr:[16:7->32:4]
-posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->32:4]
-posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->32:4]
-posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->32:4]
-posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->32:4]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:4->32:10]
+JSX <div:[15:5->15:8] > _children:16:7
 posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->18:24]
 Completable: Cpath Value[someString]->st <<jsx>>
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -108,14 +102,8 @@ posCursor:[20:27] posNoWhite:[20:26] Found expr:[9:4->32:10]
 posCursor:[20:27] posNoWhite:[20:26] Found expr:[10:4->32:10]
 posCursor:[20:27] posNoWhite:[20:26] Found expr:[11:4->32:10]
 posCursor:[20:27] posNoWhite:[20:26] Found expr:[12:4->32:10]
-posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:5->32:10]
-JSX <div:[15:5->15:8] > _children:15:8
-posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:8->32:4]
-posCursor:[20:27] posNoWhite:[20:26] Found expr:[16:7->32:4]
-posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->32:4]
-posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->32:4]
-posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->32:4]
-posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->32:4]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:4->32:10]
+JSX <div:[15:5->15:8] > _children:16:7
 posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->20:27]
 Completable: Cpath string->st <<jsx>>
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -151,14 +139,8 @@ posCursor:[22:40] posNoWhite:[22:39] Found expr:[9:4->32:10]
 posCursor:[22:40] posNoWhite:[22:39] Found expr:[10:4->32:10]
 posCursor:[22:40] posNoWhite:[22:39] Found expr:[11:4->32:10]
 posCursor:[22:40] posNoWhite:[22:39] Found expr:[12:4->32:10]
-posCursor:[22:40] posNoWhite:[22:39] Found expr:[15:5->32:10]
-JSX <div:[15:5->15:8] > _children:15:8
-posCursor:[22:40] posNoWhite:[22:39] Found expr:[15:8->32:4]
-posCursor:[22:40] posNoWhite:[22:39] Found expr:[16:7->32:4]
-posCursor:[22:40] posNoWhite:[22:39] Found expr:[17:7->32:4]
-posCursor:[22:40] posNoWhite:[22:39] Found expr:[17:7->32:4]
-posCursor:[22:40] posNoWhite:[22:39] Found expr:[22:10->32:4]
-posCursor:[22:40] posNoWhite:[22:39] Found expr:[22:10->32:4]
+posCursor:[22:40] posNoWhite:[22:39] Found expr:[15:4->32:10]
+JSX <div:[15:5->15:8] > _children:16:7
 posCursor:[22:40] posNoWhite:[22:39] Found expr:[22:10->22:40]
 Completable: Cpath Value[String, trim](Nolabel)->st <<jsx>>
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -196,14 +178,8 @@ posCursor:[24:19] posNoWhite:[24:18] Found expr:[9:4->32:10]
 posCursor:[24:19] posNoWhite:[24:18] Found expr:[10:4->32:10]
 posCursor:[24:19] posNoWhite:[24:18] Found expr:[11:4->32:10]
 posCursor:[24:19] posNoWhite:[24:18] Found expr:[12:4->32:10]
-posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:5->32:10]
-JSX <div:[15:5->15:8] > _children:15:8
-posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:8->32:4]
-posCursor:[24:19] posNoWhite:[24:18] Found expr:[16:7->32:4]
-posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->32:4]
-posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->32:4]
-posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->32:4]
-posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->32:4]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:4->32:10]
+JSX <div:[15:5->15:8] > _children:16:7
 posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->0:-1]
 Completable: Cpath Value[someInt]-> <<jsx>>
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -372,14 +348,8 @@ posCursor:[26:14] posNoWhite:[26:13] Found expr:[9:4->32:10]
 posCursor:[26:14] posNoWhite:[26:13] Found expr:[10:4->32:10]
 posCursor:[26:14] posNoWhite:[26:13] Found expr:[11:4->32:10]
 posCursor:[26:14] posNoWhite:[26:13] Found expr:[12:4->32:10]
-posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:5->32:10]
-JSX <div:[15:5->15:8] > _children:15:8
-posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:8->32:4]
-posCursor:[26:14] posNoWhite:[26:13] Found expr:[16:7->32:4]
-posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->32:4]
-posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->32:4]
-posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->32:4]
-posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->32:4]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:4->32:10]
+JSX <div:[15:5->15:8] > _children:16:7
 posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->0:-1]
 Completable: Cpath int-> <<jsx>>
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -547,14 +517,8 @@ posCursor:[28:20] posNoWhite:[28:19] Found expr:[9:4->32:10]
 posCursor:[28:20] posNoWhite:[28:19] Found expr:[10:4->32:10]
 posCursor:[28:20] posNoWhite:[28:19] Found expr:[11:4->32:10]
 posCursor:[28:20] posNoWhite:[28:19] Found expr:[12:4->32:10]
-posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:5->32:10]
-JSX <div:[15:5->15:8] > _children:15:8
-posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:8->32:4]
-posCursor:[28:20] posNoWhite:[28:19] Found expr:[16:7->32:4]
-posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->32:4]
-posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->32:4]
-posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->32:4]
-posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->32:4]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:4->32:10]
+JSX <div:[15:5->15:8] > _children:16:7
 posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->28:20]
 Completable: Cpath Value[someArr]->a <<jsx>>
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -585,16 +549,10 @@ posCursor:[30:12] posNoWhite:[30:11] Found expr:[9:4->32:10]
 posCursor:[30:12] posNoWhite:[30:11] Found expr:[10:4->32:10]
 posCursor:[30:12] posNoWhite:[30:11] Found expr:[11:4->32:10]
 posCursor:[30:12] posNoWhite:[30:11] Found expr:[12:4->32:10]
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[15:5->32:10]
-JSX <div:[15:5->15:8] > _children:15:8
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[15:8->33:2]
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[16:7->33:2]
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[17:7->33:2]
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[17:7->33:2]
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[30:10->33:2]
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[30:10->33:2]
-posCursor:[30:12] posNoWhite:[30:11] Found expr:[30:10->32:10]
-JSX <di:[30:10->30:12] div[32:6->32:9]=...[32:6->32:9]> _children:32:9
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[15:4->32:10]
+JSX <div:[15:5->15:8] > _children:16:7
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[30:9->32:10]
+JSX <di:[30:10->30:12] div[32:6->32:9]=...[32:6->32:9]> _children:None
 Completable: ChtmlElement <di
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
 Resolved opens 1 Stdlib
@@ -622,7 +580,7 @@ Resolved opens 1 Stdlib
   }]
 
 Complete src/CompletionJsx.res 45:23
-posCursor:[45:23] posNoWhite:[45:22] Found expr:[45:4->45:23]
+posCursor:[45:23] posNoWhite:[45:22] Found expr:[45:3->45:23]
 JSX <CompWithoutJsxPpx:[45:4->45:21] n[45:22->45:23]=...[45:22->45:23]> _children:None
 Completable: Cjsx([CompWithoutJsxPpx], n, [n])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -637,7 +595,7 @@ Path CompWithoutJsxPpx.make
   }]
 
 Complete src/CompletionJsx.res 48:27
-posCursor:[48:27] posNoWhite:[48:26] Found expr:[48:4->48:28]
+posCursor:[48:27] posNoWhite:[48:26] Found expr:[48:3->48:28]
 JSX <SomeComponent:[48:4->48:17] someProp[48:18->48:26]=...[48:18->48:26]> _children:None
 Completable: Cexpression CJsxPropValue [SomeComponent] someProp
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -656,7 +614,7 @@ Path SomeComponent.make
   }]
 
 Complete src/CompletionJsx.res 51:11
-posCursor:[51:11] posNoWhite:[51:10] Found expr:[51:4->51:11]
+posCursor:[51:11] posNoWhite:[51:10] Found expr:[51:3->51:11]
 JSX <h1:[51:4->51:6] hidd[51:7->51:11]=...[51:7->51:11]> _children:None
 Completable: Cjsx([h1], hidd, [hidd])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -672,7 +630,7 @@ Path JsxDOM.domProps
   }]
 
 Complete src/CompletionJsx.res 61:30
-posCursor:[61:30] posNoWhite:[61:28] Found expr:[61:4->61:29]
+posCursor:[61:30] posNoWhite:[61:28] Found expr:[61:3->61:29]
 JSX <IntrinsicElementLowercase:[61:4->61:29] > _children:None
 Completable: Cjsx([IntrinsicElementLowercase], "", [])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -699,7 +657,7 @@ Path IntrinsicElementLowercase.make
   }]
 
 Complete src/CompletionJsx.res 73:36
-posCursor:[73:36] posNoWhite:[73:35] Found expr:[73:4->73:41]
+posCursor:[73:36] posNoWhite:[73:35] Found expr:[73:3->73:41]
 JSX <MultiPropComp:[73:4->73:17] name[73:18->73:22]=...[73:23->73:30] time[73:31->73:35]=...[73:37->73:40]> _children:None
 Completable: Cexpression CJsxPropValue [MultiPropComp] time
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -725,7 +683,7 @@ Path MultiPropComp.make
   }]
 
 Complete src/CompletionJsx.res 76:36
-posCursor:[76:36] posNoWhite:[76:35] Found expr:[76:4->76:40]
+posCursor:[76:36] posNoWhite:[76:35] Found expr:[76:3->76:40]
 JSX <MultiPropComp:[76:4->76:17] name[76:18->76:22]=...[76:23->76:30] time[76:31->76:35]=...[76:37->76:40]> _children:None
 Completable: Cexpression CJsxPropValue [MultiPropComp] time
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -751,7 +709,7 @@ Path MultiPropComp.make
   }]
 
 Complete src/CompletionJsx.res 79:28
-posCursor:[79:28] posNoWhite:[79:27] Found expr:[79:4->79:32]
+posCursor:[79:28] posNoWhite:[79:27] Found expr:[79:3->79:32]
 JSX <MultiPropComp:[79:4->79:17] name[79:18->79:22]=...[79:18->79:22] time[79:23->79:27]=...[79:29->79:32]> _children:None
 Completable: Cexpression CJsxPropValue [MultiPropComp] time
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -777,8 +735,8 @@ Path MultiPropComp.make
   }]
 
 Complete src/CompletionJsx.res 89:26
-posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->89:27]
-JSX <Info:[89:4->89:8] _type[89:9->89:14]=...[89:16->89:24]> _children:89:26
+posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:3->89:27]
+JSX <Info:[89:4->89:8] _type[89:9->89:14]=...[89:16->89:24]> _children:None
 Completable: Cjsx([Info], "", [_type])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
 Resolved opens 1 Stdlib
@@ -792,10 +750,8 @@ Path Info.make
   }]
 
 Complete src/CompletionJsx.res 93:19
-posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:12->93:24]
-JSX <p:[93:12->93:13] > _children:93:13
-posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:13->93:20]
-posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:15->93:20]
+posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:11->93:24]
+JSX <p:[93:12->93:13] > _children:93:15
 posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:15->93:19]
 Pexp_field [93:15->93:17] s:[93:18->93:19]
 Completable: Cpath string.s
diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
index ad2b74dba3..686f4f5ec9 100644
--- a/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
+++ b/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
@@ -1,5 +1,5 @@
 Complete src/CompletionJsxProps.res 0:47
-posCursor:[0:47] posNoWhite:[0:46] Found expr:[0:12->0:47]
+posCursor:[0:47] posNoWhite:[0:46] Found expr:[0:11->0:47]
 JSX <CompletionSupport.TestComponent:[0:12->0:43] on[0:44->0:46]=...__ghost__[0:-1->0:-1]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -21,7 +21,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 3:48
-posCursor:[3:48] posNoWhite:[3:47] Found expr:[3:12->3:48]
+posCursor:[3:48] posNoWhite:[3:47] Found expr:[3:11->3:48]
 JSX <CompletionSupport.TestComponent:[3:12->3:43] on[3:44->3:46]=...[3:47->3:48]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on=t
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -44,7 +44,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 6:50
-posCursor:[6:50] posNoWhite:[6:49] Found expr:[6:12->6:50]
+posCursor:[6:50] posNoWhite:[6:49] Found expr:[6:11->6:50]
 JSX <CompletionSupport.TestComponent:[6:12->6:43] test[6:44->6:48]=...[6:49->6:50]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] test=T
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -140,7 +140,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 9:52
-posCursor:[9:52] posNoWhite:[9:51] Found expr:[9:12->9:52]
+posCursor:[9:52] posNoWhite:[9:51] Found expr:[9:11->9:52]
 JSX <CompletionSupport.TestComponent:[9:12->9:43] polyArg[9:44->9:51]=...__ghost__[0:-1->0:-1]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -182,7 +182,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 12:54
-posCursor:[12:54] posNoWhite:[12:53] Found expr:[12:12->12:54]
+posCursor:[12:54] posNoWhite:[12:53] Found expr:[12:11->12:54]
 JSX <CompletionSupport.TestComponent:[12:12->12:43] polyArg[12:44->12:51]=...[12:52->12:54]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg=#t
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -216,7 +216,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 15:22
-posCursor:[15:22] posNoWhite:[15:21] Found expr:[15:12->15:25]
+posCursor:[15:22] posNoWhite:[15:21] Found expr:[15:11->15:25]
 JSX <div:[15:12->15:15] muted[15:16->15:21]=...__ghost__[0:-1->0:-1]> _children:None
 Completable: Cexpression CJsxPropValue [div] muted
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -239,7 +239,7 @@ Path JsxDOM.domProps
   }]
 
 Complete src/CompletionJsxProps.res 18:29
-posCursor:[18:29] posNoWhite:[18:28] Found expr:[18:12->18:32]
+posCursor:[18:29] posNoWhite:[18:28] Found expr:[18:11->18:32]
 JSX <div:[18:12->18:15] onMouseEnter[18:16->18:28]=...__ghost__[0:-1->0:-1]> _children:None
 Completable: Cexpression CJsxPropValue [div] onMouseEnter
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -259,7 +259,7 @@ Path JsxDOM.domProps
   }]
 
 Complete src/CompletionJsxProps.res 22:52
-posCursor:[22:52] posNoWhite:[22:51] Found expr:[22:12->22:52]
+posCursor:[22:52] posNoWhite:[22:51] Found expr:[22:11->22:52]
 JSX <CompletionSupport.TestComponent:[22:12->22:43] testArr[22:44->22:51]=...__ghost__[0:-1->0:-1]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] testArr
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -278,7 +278,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 26:54
-posCursor:[26:54] posNoWhite:[26:53] Found expr:[26:12->26:56]
+posCursor:[26:54] posNoWhite:[26:53] Found expr:[26:11->26:56]
 JSX <CompletionSupport.TestComponent:[26:12->26:43] testArr[26:44->26:51]=...[26:53->26:55]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] testArr->array
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -312,7 +312,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 31:53
-posCursor:[31:53] posNoWhite:[31:52] Found expr:[31:12->31:54]
+posCursor:[31:53] posNoWhite:[31:52] Found expr:[31:11->31:54]
 JSX <CompletionSupport.TestComponent:[31:12->31:43] polyArg[31:44->31:51]=...[31:52->31:54]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg->recordBody
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -354,7 +354,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 34:49
-posCursor:[34:49] posNoWhite:[34:48] Found expr:[34:12->34:50]
+posCursor:[34:49] posNoWhite:[34:48] Found expr:[34:11->34:50]
 JSX <CompletionSupport.TestComponent:[34:12->34:43] on[34:44->34:46]=...[34:48->34:49]> _children:None
 Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on=t->recordBody
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -382,7 +382,7 @@ Path CompletionSupport.TestComponent.make
   }]
 
 Complete src/CompletionJsxProps.res 44:44
-posCursor:[44:44] posNoWhite:[44:43] Found expr:[44:12->44:44]
+posCursor:[44:44] posNoWhite:[44:43] Found expr:[44:11->44:44]
 JSX <CompletableComponentLazy:[44:12->44:36] status[44:37->44:43]=...__ghost__[0:-1->0:-1]> _children:None
 Completable: Cexpression CJsxPropValue [CompletableComponentLazy] status
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
diff --git a/tests/analysis_tests/tests/src/expected/Div.res.txt b/tests/analysis_tests/tests/src/expected/Div.res.txt
index 1fb532b983..deb951ada8 100644
--- a/tests/analysis_tests/tests/src/expected/Div.res.txt
+++ b/tests/analysis_tests/tests/src/expected/Div.res.txt
@@ -2,7 +2,7 @@ Hover src/Div.res 0:10
 {"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}
 
 Complete src/Div.res 3:17
-posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:4->3:17]
+posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:3->3:17]
 JSX <div:[3:4->3:7] dangerous[3:8->3:17]=...[3:8->3:17]> _children:None
 Completable: Cjsx([div], dangerous, [dangerous])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
diff --git a/tests/analysis_tests/tests/src/expected/Fragment.res.txt b/tests/analysis_tests/tests/src/expected/Fragment.res.txt
index 3b67cf3a42..2dba106a7d 100644
--- a/tests/analysis_tests/tests/src/expected/Fragment.res.txt
+++ b/tests/analysis_tests/tests/src/expected/Fragment.res.txt
@@ -3,15 +3,8 @@ Hover src/Fragment.res 6:19
 
 Hover src/Fragment.res 9:56
 Nothing at that position. Now trying to use completion.
-posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:10->9:67]
-posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:13->9:67]
-posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:13->9:66]
-JSX <SectionHeader:[9:13->9:26] > _children:9:26
-posCursor:[9:56] posNoWhite:[9:55] Found expr:__ghost__[9:10->9:67]
-Pexp_construct []:__ghost__[9:10->9:67] None
-Completable: Cexpression CTypeAtPos()=[]->variantPayload::::($1)
-Package opens Stdlib.place holder Pervasives.JsxModules.place holder
-Resolved opens 1 Stdlib
-ContextPath CTypeAtPos()
+posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:9->9:70]
+posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:12->9:66]
+JSX <SectionHeader:[9:13->9:26] > _children:9:29
 null
 
diff --git a/tests/analysis_tests/tests/src/expected/Highlight.res.txt b/tests/analysis_tests/tests/src/expected/Highlight.res.txt
index db7155bfdd..6ee7e2e800 100644
--- a/tests/analysis_tests/tests/src/expected/Highlight.res.txt
+++ b/tests/analysis_tests/tests/src/expected/Highlight.res.txt
@@ -6,32 +6,37 @@ Lident: Component 1:13 Namespace
 Variable: _c [4:4->4:6]
 JsxTag <: 4:9
 Lident: Component 4:10 Namespace
+JsxTag />: 4:20
 Variable: _mc [6:4->6:7]
 JsxTag <: 6:10
 Ldot: M 6:11 Namespace
 Lident: C 6:13 Namespace
+JsxTag />: 6:15
 Variable: _d [8:4->8:6]
 JsxTag <: 8:9
 Lident: div 8:10 JsxLowercase
+JsxTag />: 8:14
 Variable: _d2 [10:4->10:7]
 JsxTag <: 11:2
 Lident: div 11:3 JsxLowercase
-Lident: div 16:4 JsxLowercase
 JsxTag >: 11:6
-JsxTag >: 16:7
 Ldot: React 12:5 Namespace
 Lident: string 12:11 Variable
 JsxTag <: 13:4
 Lident: div 13:5 JsxLowercase
-Lident: div 13:34 JsxLowercase
 JsxTag >: 13:8
-JsxTag >: 13:37
 Ldot: React 13:11 Namespace
 Lident: string 13:17 Variable
+JsxTag </: 13:32
+Lident: div 13:34 JsxLowercase
+JsxTag >: 13:37
 Ldot: React 14:5 Namespace
 Lident: string 14:11 Variable
 Ldot: React 15:5 Namespace
 Lident: string 15:11 Variable
+JsxTag </: 16:2
+Lident: div 16:4 JsxLowercase
+JsxTag >: 16:7
 Lident: pair 18:5 Type
 Lident: looooooooooooooooooooooooooooooooooooooong_int 20:5 Type
 Lident: int 20:54 Type
@@ -84,11 +89,13 @@ Lident: world 69:39 Variable
 Lident: add 71:8 Variable
 JsxTag <: 73:8
 Lident: div 73:9 JsxLowercase
-Lident: div 73:36 JsxLowercase
 JsxTag >: 73:24
-JsxTag >: 73:39
 JsxTag <: 73:26
 Lident: div 73:27 JsxLowercase
+JsxTag />: 73:31
+JsxTag </: 73:34
+Lident: div 73:36 JsxLowercase
+JsxTag >: 73:39
 Lident: SomeComponent 75:7 Namespace
 Lident: Nested 76:9 Namespace
 Variable: make [78:8->78:12]
@@ -97,12 +104,14 @@ Lident: children 79:10 Variable
 JsxTag <: 84:8
 Ldot: SomeComponent 84:9 Namespace
 Lident: Nested 84:23 Namespace
-Ldot: SomeComponent 84:41 Namespace
-Lident: Nested 84:55 Namespace
 JsxTag >: 84:29
-JsxTag >: 84:61
 JsxTag <: 84:31
 Lident: div 84:32 JsxLowercase
+JsxTag />: 84:36
+JsxTag </: 84:39
+Ldot: SomeComponent 84:41 Namespace
+Lident: Nested 84:55 Namespace
+JsxTag >: 84:61
 Variable: toAs [90:4->90:8]
 Variable: x [90:19->90:20]
 Lident: x 90:25 Variable
@@ -121,6 +130,7 @@ Lident: int 101:14 Variable
 Lident: to 101:18 Variable
 JsxTag <: 104:8
 Lident: ToAsProp 104:9 Namespace
+JsxTag />: 104:23
 Variable: true [107:4->107:11]
 Lident: true 108:8->108:15 Variable
 Variable: enumInModule [110:4->110:16]
diff --git a/tests/analysis_tests/tests/src/expected/Hover.res.txt b/tests/analysis_tests/tests/src/expected/Hover.res.txt
index 8d692baed4..31a6fa21a6 100644
--- a/tests/analysis_tests/tests/src/expected/Hover.res.txt
+++ b/tests/analysis_tests/tests/src/expected/Hover.res.txt
@@ -50,14 +50,14 @@ Hover src/Hover.res 77:7
 
 Hover src/Hover.res 91:10
 Nothing at that position. Now trying to use completion.
-posCursor:[91:10] posNoWhite:[91:8] Found expr:[88:3->91:9]
-JSX <Comp:[88:3->88:7] > _children:88:7
+posCursor:[91:10] posNoWhite:[91:8] Found expr:[88:2->91:9]
+JSX <Comp:[88:3->88:7] > _children:89:4
 null
 
 Hover src/Hover.res 98:10
 Nothing at that position. Now trying to use completion.
-posCursor:[98:10] posNoWhite:[98:9] Found expr:[95:3->98:10]
-JSX <Comp1:[95:3->95:8] > _children:95:8
+posCursor:[98:10] posNoWhite:[98:9] Found expr:[95:2->98:10]
+JSX <Comp1:[95:3->95:8] > _children:96:4
 null
 
 Hover src/Hover.res 103:25
diff --git a/tests/analysis_tests/tests/src/expected/Jsx2.res.txt b/tests/analysis_tests/tests/src/expected/Jsx2.res.txt
index 76c895c556..66920cb633 100644
--- a/tests/analysis_tests/tests/src/expected/Jsx2.res.txt
+++ b/tests/analysis_tests/tests/src/expected/Jsx2.res.txt
@@ -2,7 +2,7 @@ Definition src/Jsx2.res 5:9
 {"uri": "Jsx2.res", "range": {"start": {"line": 2, "character": 6}, "end": {"line": 2, "character": 10}}}
 
 Complete src/Jsx2.res 8:15
-posCursor:[8:15] posNoWhite:[8:14] Found expr:[8:4->8:15]
+posCursor:[8:15] posNoWhite:[8:14] Found expr:[8:3->8:15]
 JSX <M:[8:4->8:5] second[8:6->8:12]=...[8:13->8:15]> _children:None
 Completable: Cexpression CJsxPropValue [M] second=fi
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -12,7 +12,7 @@ Path M.make
 []
 
 Complete src/Jsx2.res 11:20
-posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:4->11:20]
+posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:3->11:20]
 JSX <M:[11:4->11:5] second[11:6->11:12]=...[11:13->11:18] f[11:19->11:20]=...[11:19->11:20]> _children:None
 Completable: Cjsx([M], f, [second, f])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -33,7 +33,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 14:13
-posCursor:[14:13] posNoWhite:[14:12] Found expr:[14:12->14:13]
+posCursor:[14:13] posNoWhite:[14:12] Found expr:[14:11->14:13]
 JSX <M:[14:12->14:13] > _children:None
 Completable: Cpath Module[M]
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -71,7 +71,7 @@ Path M
   }]
 
 Complete src/Jsx2.res 22:19
-posCursor:[22:19] posNoWhite:[22:18] Found expr:[22:4->22:19]
+posCursor:[22:19] posNoWhite:[22:18] Found expr:[22:3->22:19]
 JSX <M:[22:4->22:5] prop[22:6->22:10]=...[22:12->22:16] k[22:18->22:19]=...[22:18->22:19]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -86,7 +86,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 25:17
-posCursor:[25:17] posNoWhite:[25:16] Found expr:[25:4->25:17]
+posCursor:[25:17] posNoWhite:[25:16] Found expr:[25:3->25:17]
 JSX <M:[25:4->25:5] prop[25:6->25:10]=...[25:11->25:15] k[25:16->25:17]=...[25:16->25:17]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -101,7 +101,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 28:21
-posCursor:[28:21] posNoWhite:[28:20] Found expr:[28:4->28:21]
+posCursor:[28:21] posNoWhite:[28:20] Found expr:[28:3->28:21]
 JSX <M:[28:4->28:5] prop[28:6->28:10]=...[28:11->28:19] k[28:20->28:21]=...[28:20->28:21]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -116,7 +116,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 31:24
-posCursor:[31:24] posNoWhite:[31:23] Found expr:[31:4->31:24]
+posCursor:[31:24] posNoWhite:[31:23] Found expr:[31:3->31:24]
 JSX <M:[31:4->31:5] prop[31:6->31:10]=...[31:11->31:22] k[31:23->31:24]=...[31:23->31:24]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -131,8 +131,8 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 34:18
-posCursor:[34:18] posNoWhite:[34:17] Found expr:[34:4->34:18]
-JSX <M:[34:4->34:5] prop[34:6->34:10]=...[34:12->34:16] k[34:17->34:18]=...[34:17->34:18]> _children:None
+posCursor:[34:18] posNoWhite:[34:17] Found expr:[34:3->34:18]
+JSX <M:[34:4->34:5] prop[34:6->34:10]=...[34:11->34:16] k[34:17->34:18]=...[34:17->34:18]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
 Resolved opens 1 Stdlib
@@ -146,7 +146,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 37:16
-posCursor:[37:16] posNoWhite:[37:15] Found expr:[37:4->37:16]
+posCursor:[37:16] posNoWhite:[37:15] Found expr:[37:3->37:16]
 JSX <M:[37:4->37:5] prop[37:6->37:10]=...[37:11->37:14] k[37:15->37:16]=...[37:15->37:16]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -161,7 +161,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 40:17
-posCursor:[40:17] posNoWhite:[40:16] Found expr:[40:4->40:17]
+posCursor:[40:17] posNoWhite:[40:16] Found expr:[40:3->40:17]
 JSX <M:[40:4->40:5] prop[40:6->40:10]=...[40:11->40:15] k[40:16->40:17]=...[40:16->40:17]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -176,7 +176,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 43:18
-posCursor:[43:18] posNoWhite:[43:17] Found expr:[43:4->43:18]
+posCursor:[43:18] posNoWhite:[43:17] Found expr:[43:3->43:18]
 JSX <M:[43:4->43:5] prop[43:6->43:10]=...[43:11->43:16] k[43:17->43:18]=...[43:17->43:18]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -191,7 +191,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 46:16
-posCursor:[46:16] posNoWhite:[46:15] Found expr:[46:4->46:16]
+posCursor:[46:16] posNoWhite:[46:15] Found expr:[46:3->46:16]
 JSX <M:[46:4->46:5] prop[46:6->46:10]=...[46:11->46:14] k[46:15->46:16]=...[46:15->46:16]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -206,7 +206,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 49:27
-posCursor:[49:27] posNoWhite:[49:26] Found expr:[49:4->49:27]
+posCursor:[49:27] posNoWhite:[49:26] Found expr:[49:3->49:27]
 JSX <M:[49:4->49:5] prop[49:6->49:10]=...[49:11->49:25] k[49:26->49:27]=...[49:26->49:27]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -221,7 +221,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 52:38
-posCursor:[52:38] posNoWhite:[52:37] Found expr:[52:4->52:38]
+posCursor:[52:38] posNoWhite:[52:37] Found expr:[52:3->52:38]
 JSX <M:[52:4->52:5] prop[52:6->52:10]=...[52:11->52:36] k[52:37->52:38]=...[52:37->52:38]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -236,7 +236,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 55:25
-posCursor:[55:25] posNoWhite:[55:24] Found expr:[55:4->55:25]
+posCursor:[55:25] posNoWhite:[55:24] Found expr:[55:3->55:25]
 JSX <M:[55:4->55:5] prop[55:6->55:10]=...[55:11->55:23] k[55:24->55:25]=...[55:24->55:25]> _children:None
 Completable: Cjsx([M], k, [prop, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -254,7 +254,7 @@ Definition src/Jsx2.res 58:11
 {"uri": "Component.res", "range": {"start": {"line": 1, "character": 4}, "end": {"line": 1, "character": 8}}}
 
 Complete src/Jsx2.res 68:10
-posCursor:[68:10] posNoWhite:[68:9] Found expr:[68:4->68:10]
+posCursor:[68:10] posNoWhite:[68:9] Found expr:[68:3->68:10]
 JSX <Ext:[68:4->68:7] al[68:8->68:10]=...[68:8->68:10]> _children:None
 Completable: Cjsx([Ext], al, [al])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -269,7 +269,7 @@ Path Ext.make
   }]
 
 Complete src/Jsx2.res 71:11
-posCursor:[71:11] posNoWhite:[71:10] Found expr:[71:4->71:11]
+posCursor:[71:11] posNoWhite:[71:10] Found expr:[71:3->71:11]
 JSX <M:[71:4->71:5] first[71:6->71:11]=...[71:6->71:11]> _children:None
 Completable: Cjsx([M], first, [first])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -278,7 +278,7 @@ Path M.make
 []
 
 Complete src/Jsx2.res 74:16
-posCursor:[74:16] posNoWhite:[74:15] Found expr:[74:4->74:16]
+posCursor:[74:16] posNoWhite:[74:15] Found expr:[74:3->74:16]
 JSX <M:[74:4->74:5] first[74:6->74:11]=...[74:12->74:14] k[74:15->74:16]=...[74:15->74:16]> _children:None
 Completable: Cjsx([M], k, [first, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -293,7 +293,7 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 77:23
-posCursor:[77:23] posNoWhite:[77:22] Found expr:[77:4->77:23]
+posCursor:[77:23] posNoWhite:[77:22] Found expr:[77:3->77:23]
 JSX <M:[77:4->77:5] first[77:6->77:11]=...[77:19->77:21] k[77:22->77:23]=...[77:22->77:23]> _children:None
 Completable: Cjsx([M], k, [first, k])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -308,22 +308,16 @@ Path M.make
   }]
 
 Complete src/Jsx2.res 80:6
-posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:4->85:69]
-Pexp_apply ...[83:20->83:21] (...[80:4->83:19], ...[84:2->85:69])
-posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:4->83:19]
-JSX <M:[80:4->80:5] > _children:80:5
-posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:5->83:20]
-posCursor:[80:6] posNoWhite:[80:5] Found expr:__ghost__[80:5->83:20]
-Pexp_construct []:__ghost__[80:5->83:20] None
-posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:4->83:19]
-JSX <M:[80:4->80:5] > _children:80:5
-posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:5->83:20]
-posCursor:[80:6] posNoWhite:[80:5] Found expr:__ghost__[80:5->83:20]
-Pexp_construct []:__ghost__[80:5->83:20] None
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:3->85:69]
+Pexp_apply ...[83:20->83:21] (...[80:3->83:19], ...[84:2->85:69])
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:3->83:19]
+JSX <M:[80:4->80:5] > _children:83:0
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:3->83:19]
+JSX <M:[80:4->80:5] > _children:83:0
 []
 
 Complete src/Jsx2.res 89:16
-posCursor:[89:16] posNoWhite:[89:15] Found expr:[89:4->89:16]
+posCursor:[89:16] posNoWhite:[89:15] Found expr:[89:3->89:16]
 JSX <WithChildren:[89:4->89:16] > _children:None
 Completable: Cpath Module[WithChildren]
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -339,7 +333,7 @@ Path WithChildren
   }]
 
 Complete src/Jsx2.res 91:18
-posCursor:[91:18] posNoWhite:[91:17] Found expr:[91:4->91:18]
+posCursor:[91:18] posNoWhite:[91:17] Found expr:[91:3->91:18]
 JSX <WithChildren:[91:4->91:16] n[91:17->91:18]=...[91:17->91:18]> _children:None
 Completable: Cjsx([WithChildren], n, [n])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -435,8 +429,8 @@ Path DefineSomeFields.th
   }]
 
 Complete src/Jsx2.res 122:20
-posCursor:[122:20] posNoWhite:[122:19] Found expr:[121:3->125:4]
-JSX <div:[121:3->121:6] x[122:5->122:6]=...[122:7->122:20] name[124:4->124:8]=...[124:9->124:11]> _children:125:2
+posCursor:[122:20] posNoWhite:[122:19] Found expr:[121:2->125:4]
+JSX <div:[121:3->121:6] x[122:5->122:6]=...[122:7->122:20] name[124:4->124:8]=...[124:9->124:11]> _children:None
 posCursor:[122:20] posNoWhite:[122:19] Found expr:[122:7->122:20]
 Pexp_ident Outer.Inner.h:[122:7->122:20]
 Completable: Cpath Value[Outer, Inner, h]
@@ -453,7 +447,7 @@ Path Outer.Inner.h
   }]
 
 Complete src/Jsx2.res 129:19
-posCursor:[129:19] posNoWhite:[129:18] Found expr:[128:3->131:9]
+posCursor:[129:19] posNoWhite:[129:18] Found expr:[128:2->131:9]
 JSX <div:[128:3->128:6] x[129:5->129:6]=...[129:7->131:8]> _children:None
 posCursor:[129:19] posNoWhite:[129:18] Found expr:[129:7->131:8]
 Pexp_ident Outer.Inner.:[129:7->131:8]
@@ -471,7 +465,7 @@ Path Outer.Inner.
   }]
 
 Complete src/Jsx2.res 136:7
-posCursor:[136:7] posNoWhite:[136:6] Found expr:[135:3->138:9]
+posCursor:[136:7] posNoWhite:[136:6] Found expr:[135:2->138:9]
 JSX <div:[135:3->135:6] x[136:5->136:6]=...[138:4->138:8]> _children:None
 Completable: Cexpression CJsxPropValue [div] x
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -491,8 +485,8 @@ Path JsxDOM.domProps
   }]
 
 Complete src/Jsx2.res 150:21
-posCursor:[150:21] posNoWhite:[150:20] Found expr:[150:12->150:32]
-JSX <Nested.Co:[150:12->150:21] name[150:22->150:26]=...[150:27->150:29]> _children:150:30
+posCursor:[150:21] posNoWhite:[150:20] Found expr:[150:11->150:32]
+JSX <Nested.Co:[150:12->150:21] name[150:22->150:26]=...[150:27->150:29]> _children:None
 Completable: Cpath Module[Nested, Co]
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
 Resolved opens 1 Stdlib
@@ -507,7 +501,7 @@ Path Nested.Co
   }]
 
 Complete src/Jsx2.res 153:19
-posCursor:[153:19] posNoWhite:[153:18] Found expr:[153:12->153:25]
+posCursor:[153:19] posNoWhite:[153:18] Found expr:[153:11->153:25]
 JSX <Nested.:[153:12->153:24] > _children:None
 Completable: Cpath Module[Nested, ""]
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
@@ -523,28 +517,8 @@ Path Nested.
   }]
 
 Hover src/Jsx2.res 162:12
-Nothing at that position. Now trying to use completion.
-posCursor:[162:12] posNoWhite:[162:11] Found expr:[162:3->162:21]
-posCursor:[162:12] posNoWhite:[162:11] Found expr:[162:6->162:21]
-posCursor:[162:12] posNoWhite:[162:11] Found expr:[162:6->162:20]
-JSX <Comp:[162:6->162:10] age[162:11->162:14]=...[162:15->162:17]> _children:162:18
-Completable: Cjsx([Comp], age, [age])
-Package opens Stdlib.place holder Pervasives.JsxModules.place holder
-Resolved opens 1 Stdlib
-Path Comp.make
-{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+{"contents": {"kind": "markdown", "value": "```rescript\nComp.props<int>\n```\n\n---\n\n```\n \n```\n```rescript\ntype Comp.props<'age> = {age: 'age}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx2.res%22%2C157%2C2%5D)\n"}}
 
 Hover src/Jsx2.res 167:16
-Nothing at that position. Now trying to use completion.
-posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:3->167:30]
-posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:7->167:30]
-posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:7->167:25]
-posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:10->167:25]
-posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:10->167:24]
-JSX <Comp:[167:10->167:14] age[167:15->167:18]=...[167:19->167:21]> _children:167:22
-Completable: Cjsx([Comp], age, [age])
-Package opens Stdlib.place holder Pervasives.JsxModules.place holder
-Resolved opens 1 Stdlib
-Path Comp.make
-{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+{"contents": {"kind": "markdown", "value": "```rescript\nComp.props<int>\n```\n\n---\n\n```\n \n```\n```rescript\ntype Comp.props<'age> = {age: 'age}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx2.res%22%2C157%2C2%5D)\n"}}
 
diff --git a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt
index cca05ca41c..ba54c5d047 100644
--- a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt
+++ b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt
@@ -2,7 +2,7 @@ Definition src/JsxV4.res 8:9
 {"uri": "JsxV4.res", "range": {"start": {"line": 5, "character": 6}, "end": {"line": 5, "character": 10}}}
 
 Complete src/JsxV4.res 11:20
-posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:4->11:20]
+posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:3->11:20]
 JSX <M4:[11:4->11:6] first[11:7->11:12]=...[11:13->11:18] f[11:19->11:20]=...[11:19->11:20]> _children:None
 Completable: Cjsx([M4], f, [first, f])
 Package opens Stdlib.place holder Pervasives.JsxModules.place holder
diff --git a/tests/analysis_tests/tests/src/expected/RecoveryOnProp.res.txt b/tests/analysis_tests/tests/src/expected/RecoveryOnProp.res.txt
index 6533452eb5..32cd986e2c 100644
--- a/tests/analysis_tests/tests/src/expected/RecoveryOnProp.res.txt
+++ b/tests/analysis_tests/tests/src/expected/RecoveryOnProp.res.txt
@@ -1,5 +1,5 @@
 Complete src/RecoveryOnProp.res 6:26
-posCursor:[6:26] posNoWhite:[6:25] Found expr:[3:3->11:8]
+posCursor:[6:26] posNoWhite:[6:25] Found expr:[3:2->11:8]
 JSX <div:[3:3->3:6] onClick[4:4->4:11]=...[4:13->0:-1]> _children:None
 posCursor:[6:26] posNoWhite:[6:25] Found expr:[4:13->8:6]
 posCursor:[6:26] posNoWhite:[6:25] Found expr:[5:6->8:5]
diff --git a/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt b/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt
index f151681291..b09e877523 100644
--- a/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt
+++ b/tests/syntax_tests/data/ast-mapping/expected/JSXElements.res.txt
@@ -2,9 +2,19 @@ let emptyUnary = ReactDOM.jsx("input", {})
 
 let emptyNonunary = ReactDOM.jsx("div", {})
 
-let emptyUnaryWithAttributes = ReactDOM.jsx("input", {type_: "text"})
+let emptyUnaryWithAttributes = ReactDOM.jsx(
+  "input",
+  {
+    type_: "text",
+  },
+)
 
-let emptyNonunaryWithAttributes = ReactDOM.jsx("div", {className: "container"})
+let emptyNonunaryWithAttributes = ReactDOM.jsx(
+  "div",
+  {
+    className: "container",
+  },
+)
 
 let elementWithChildren = ReactDOM.jsxs(
   "div",
diff --git a/tests/syntax_tests/data/ast-mapping/expected/JSXFragments.res.txt b/tests/syntax_tests/data/ast-mapping/expected/JSXFragments.res.txt
index 6ed9d65c47..1762c37f26 100644
--- a/tests/syntax_tests/data/ast-mapping/expected/JSXFragments.res.txt
+++ b/tests/syntax_tests/data/ast-mapping/expected/JSXFragments.res.txt
@@ -1,6 +1,11 @@
 let empty = React.jsx(React.jsxFragment, {})
 
-let fragmentWithBracedExpresssion = React.jsx(React.jsxFragment, {children: {React.int(1 + 2)}})
+let fragmentWithBracedExpresssion = React.jsx(
+  React.jsxFragment,
+  {
+    children: {React.int(1 + 2)},
+  },
+)
 
 let fragmentWithJSXElements = React.jsxs(
   React.jsxFragment,
@@ -18,7 +23,12 @@ let nestedFragments = React.jsxs(
     children: React.array([
       ReactDOM.jsx("h1", {children: ?ReactDOM.someElement({React.string("Hi")})}),
       ReactDOM.jsx("p", {children: ?ReactDOM.someElement({React.string("Hello")})}),
-      React.jsx(React.jsxFragment, {children: {React.string("Bye")}}),
+      React.jsx(
+        React.jsxFragment,
+        {
+          children: {React.string("Bye")},
+        },
+      ),
     ]),
   },
 )
diff --git a/tests/syntax_tests/data/conversion/reason/expected/string.res.txt b/tests/syntax_tests/data/conversion/reason/expected/string.res.txt
index dde635285c..217af2ddad 100644
--- a/tests/syntax_tests/data/conversion/reason/expected/string.res.txt
+++ b/tests/syntax_tests/data/conversion/reason/expected/string.res.txt
@@ -8,7 +8,7 @@ carriage return`
 let x = "\""
 let y = "\n"
 
-(<> {"\n"->React.string} </>)
+<> {"\n"->React.string} </>
 
 // The `//` should not result into an extra comment
 let x = `https://www.apple.com`
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/implementation.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/implementation.res.txt
index 76dd17499a..a22e3718b2 100644
--- a/tests/syntax_tests/data/parsing/errors/expressions/expected/implementation.res.txt
+++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/implementation.res.txt
@@ -12,7 +12,6 @@
 
 module InstallerDownload =
   struct
-    let make [arity:1]() = ((div ~children:[] ())[@res.braces ][@JSX ])
-      [@@react.component ]
+    let make [arity:1]() = ((<div />)[@res.braces ])[@@react.component ]
   end
 module LicenseList = struct  end
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
index 2853150fe2..09444d5759 100644
--- a/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
+++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
@@ -56,13 +56,9 @@
 
   I'm not sure what to parse here when looking at ".".
 
-let x = ((di-v ~children:[] ())[@JSX ])
-let x = ((Unclosed.createElement ~children:[] ())[@JSX ])
-let x =
-  ((Foo.Bar.createElement ~children:[] ())[@JSX ]) > ([%rescript.exprhole ])
-let x =
-  ((Foo.Bar.Baz.createElement ~children:[] ())[@JSX ]) >
-    ([%rescript.exprhole ])
-let x =
-  ((Foo.bar.createElement ~children:[] ())[@JSX ]) > ([%rescript.exprhole ])
-let x = ((Foo.bar.createElement ~baz ~children:[] ())[@JSX ])
\ No newline at end of file
+let x = <di-v />
+let x = <Unclosed></Unclosed>
+let x = <Foo.Bar></Foo.Bar> > ([%rescript.exprhole ])
+let x = <Foo.Bar.Baz></Foo.Bar.Baz> > ([%rescript.exprhole ])
+let x = <Foo.bar></Foo.bar> > ([%rescript.exprhole ])
+let x = <Foo.bar baz />
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/binaryNoEs6Arrow.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/binaryNoEs6Arrow.res.txt
index 71c6ce2066..260cca36aa 100644
--- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/binaryNoEs6Arrow.res.txt
+++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/binaryNoEs6Arrow.res.txt
@@ -21,13 +21,12 @@
     ((color === Black) && (color === Red)) &&
       ((sibling === None) || (parent === None))
     do () done
-;;((div
-      ~onClick:((fun [arity:1]event ->
+;;<div onClick=((fun [arity:1]event ->
                    ((match videoContainerRect with
                      | Some videoContainerRect ->
                          let newChapter =
                            ({ startTime = (percent *. duration) } : Video.chapter) in
                          { a; b } -> onChange
                      | _ -> ())
-                   [@res.braces ]))[@res.braces ]) ~children:[] ())[@JSX ])
+                   [@res.braces ]))[@res.braces ]) />
 ;;if inclusions.(index) <- (uid, url) then onChange inclusions
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/bracedOrRecord.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/bracedOrRecord.res.txt
index d561dd0ad7..cdb2da8111 100644
--- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/bracedOrRecord.res.txt
+++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/bracedOrRecord.res.txt
@@ -26,4 +26,4 @@ let f = ((fun [arity:1]event -> (event.target).value)[@res.braces ])
 let f = ((fun [arity:1]event -> ((event.target).value : string))
   [@res.braces ])
 let x = ((let a = 1 in let b = 2 in a + b)[@res.braces ])
-;;(([(({js|\n|js} -> React.string)[@res.braces ])])[@JSX ])
\ No newline at end of file
+;;<>(({js|\n|js} -> React.string)[@res.braces ])</>
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt
index 7a853a944a..cb73356815 100644
--- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt
+++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt
@@ -1,165 +1,106 @@
-let _ = ((div ~children:[] ())[@JSX ])
-let _ = ((div ~children:[] ())[@JSX ])
-let _ = ((div ~className:{js|menu|js} ~children:[] ())[@JSX ])
-let _ = ((div ~className:{js|menu|js} ~children:[] ())[@JSX ])
-let _ = ((div ~className:{js|menu|js} ~children:[] ())[@JSX ])
-let _ = ((div ~className:{js|menu|js} ~children:[] ())[@JSX ])
-let _ =
-  ((div ~className:{js|menu|js}
-      ~onClick:((fun [arity:1]_ -> Js.log {js|click|js})[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div ~className:{js|menu|js}
-      ~onClick:((fun [arity:1]_ -> Js.log {js|click|js})[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
-let _ = ((Navbar.createElement ~children:[] ())[@JSX ])
-let _ = ((Navbar.createElement ~children:[] ())[@JSX ])
-let _ = ((Navbar.createElement ~children:[] ())[@JSX ])
-let _ = ((Navbar.createElement ~className:{js|menu|js} ~children:[] ())
-  [@JSX ])
-let _ = ((Dot.Up.createElement ~children:[] ())[@JSX ])
-let _ = ((Dot.Up.createElement ~children:[] ())[@JSX ])
-let _ = ((Dot.Up.createElement ~children:[] ())[@JSX ])
-let _ =
-  ((Dot.Up.createElement
-      ~children:[((Dot.low.createElement ~children:[] ())[@JSX ])] ())
-  [@JSX ])
-let _ =
-  ((Dot.Up.createElement
-      ~children:[((Dot.Up.createElement ~children:[] ())[@JSX ])] ())
-  [@JSX ])
-let _ = ((Dot.Up.createElement ~className:{js|menu|js} ~children:[] ())
-  [@JSX ])
-let _ = ((Dot.low.createElement ~children:[] ())[@JSX ])
-let _ = ((Dot.low.createElement ~children:[] ())[@JSX ])
-let _ = ((Dot.low.createElement ~children:[] ())[@JSX ])
-let _ =
-  ((Dot.low.createElement
-      ~children:[((Dot.Up.createElement ~children:[] ())[@JSX ])] ())
-  [@JSX ])
-let _ =
-  ((Dot.low.createElement
-      ~children:[((Dot.low.createElement ~children:[] ())[@JSX ])] ())
-  [@JSX ])
-let _ = ((Dot.low.createElement ~className:{js|menu|js} ~children:[] ())
-  [@JSX ])
-let _ = ((el ~punned ~children:[] ())[@JSX ])
-let _ = ((el ?punned ~children:[] ())[@JSX ])
-let _ = ((el ~punned ~children:[] ())[@JSX ])
-let _ = ((el ?punned ~children:[] ())[@JSX ])
-let _ = ((el ?a:b ~children:[] ())[@JSX ])
-let _ = ((el ?a:b ~children:[] ())[@JSX ])
-let _ = (([])[@JSX ])
-let _ = (([])[@JSX ])
-let _ =
-  ((div ~className:{js|menu|js}
-      ~children:[((div ~className:{js|submenu|js} ~children:[sub1] ())
-                [@JSX ]);
-                ((div ~className:{js|submenu|js} ~children:[sub2] ())
-                [@JSX ])] ())
-  [@JSX ])
-let _ =
-  ((div ~className:{js|menu|js}
-      ~children:[((div ~className:{js|submenu|js} ~children:[sub1] ())
-                [@JSX ]);
-                ((div ~className:{js|submenu|js} ~children:[sub2] ())
-                [@JSX ])] ())
-  [@JSX ])
-let _ = ((div ~children:child ())[@JSX ])
-let _ = ((Foo.createElement ~children:(fun [arity:1]a -> 1) ())[@JSX ])
-let _ =
-  ((Foo.createElement ~children:((Foo2.createElement ~children:[] ())
-      [@JSX ]) ())
-  [@JSX ])
-let _ = ((Foo.createElement ~children:[|a|] ())[@JSX ])
-let _ = ((Foo.createElement ~children:(1, 2) ())[@JSX ])
-let _ = ((Foo.createElement ~children:(1, 2) ())[@JSX ])
-let _ =
-  ((div ~children:[ident; [|1;2;3|]; ((call a b)[@res.braces ]); (x.y).z] ())
-  [@JSX ])
-let _ =
-  ((Outer.createElement ~inner:((Inner.createElement ~children:[] ())
-      [@JSX ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div ~onClick:onClickHandler ~children:[(([{js|foobar|js}])[@JSX ])] ())
-  [@JSX ])
-let _ =
-  ((Window.createElement
-      ~style:{
-               width = 10;
-               height = 10;
-               paddingTop = 10;
-               paddingLeft = 10;
-               paddingRight = 10;
-               paddingBottom = 10
-             } ~children:[] ())
-  [@JSX ])
-let _ = ((OverEager.createElement ~fiber:Metal.fiber ~children:[] ())[@JSX ])
-let arrayOfListOfJsx = [|(([])[@JSX ])|]
-let arrayOfListOfJsx =
-  [|(([((Foo.createElement ~children:[] ())[@JSX ])])[@JSX ])|]
-let arrayOfListOfJsx =
-  [|(([((Foo.createElement ~children:[] ())[@JSX ])])
-    [@JSX ]);(([((Bar.createElement ~children:[] ())[@JSX ])])[@JSX ])|]
-let sameButWithSpaces = [|(([])[@JSX ])|]
-let sameButWithSpaces =
-  [|(([((Foo.createElement ~children:[] ())[@JSX ])])[@JSX ])|]
-let sameButWithSpaces =
-  [|(([((Foo.createElement ~children:[] ())[@JSX ])])
-    [@JSX ]);(([((Bar.createElement ~children:[] ())[@JSX ])])[@JSX ])|]
-let sameButWithSpaces =
-  [|(([((Foo.createElement ~children:[] ())[@JSX ])])
-    [@JSX ]);(([((Bar.createElement ~children:[] ())[@JSX ])])[@JSX ])|]
+let _ = <div></div>
+let _ = <div></div>
+let _ = <div className={js|menu|js}></div>
+let _ = <div className={js|menu|js}></div>
+let _ = <div className={js|menu|js}></div>
+let _ = <div className={js|menu|js}></div>
+let _ =
+  <div className={js|menu|js} onClick=((fun [arity:1]_ ->
+                                          Js.log {js|click|js})
+  [@res.braces ])></div>
+let _ =
+  <div className={js|menu|js} onClick=((fun [arity:1]_ ->
+                                          Js.log {js|click|js})
+  [@res.braces ])></div>
+let _ = <Navbar />
+let _ = <Navbar></Navbar>
+let _ = <Navbar></Navbar>
+let _ = <Navbar className={js|menu|js}></Navbar>
+let _ = <Dot.Up />
+let _ = <Dot.Up></Dot.Up>
+let _ = <Dot.Up></Dot.Up>
+let _ = <Dot.Up><Dot.low /></Dot.Up>
+let _ = <Dot.Up><Dot.Up /></Dot.Up>
+let _ = <Dot.Up className={js|menu|js}></Dot.Up>
+let _ = <Dot.low />
+let _ = <Dot.low></Dot.low>
+let _ = <Dot.low></Dot.low>
+let _ = <Dot.low><Dot.Up /></Dot.low>
+let _ = <Dot.low><Dot.low /></Dot.low>
+let _ = <Dot.low className={js|menu|js}></Dot.low>
+let _ = <el punned></el>
+let _ = <el ?punned></el>
+let _ = <el punned />
+let _ = <el ?punned />
+let _ = <el a=?b></el>
+let _ = <el a=?b />
+let _ = <></>
+let _ = <></>
+let _ = <div className={js|menu|js}><div className={js|submenu|js}>sub1</div>
+  <div className={js|submenu|js}>sub2</div></div>
+let _ = <div className={js|menu|js}><div className={js|submenu|js}>sub1</div>
+  <div className={js|submenu|js}>sub2</div></div>
+let _ = <div>child</div>
+let _ = <Foo>(fun [arity:1]a -> 1)</Foo>
+let _ = <Foo><Foo2 /></Foo>
+let _ = <Foo>[|a|]</Foo>
+let _ = <Foo>(1, 2)</Foo>
+let _ = <Foo>(1, 2)</Foo>
+let _ = <div>ident [|1;2;3|] ((call a b)[@res.braces ]) ((x.y).z)</div>
+let _ = <Outer inner=<Inner /> />
+let _ = <div onClick=onClickHandler><>{js|foobar|js}</></div>
+let _ =
+  <Window style={
+                  width = 10;
+                  height = 10;
+                  paddingTop = 10;
+                  paddingLeft = 10;
+                  paddingRight = 10;
+                  paddingBottom = 10
+                } />
+let _ = <OverEager fiber=Metal.fiber />
+let arrayOfListOfJsx = [|<></>|]
+let arrayOfListOfJsx = [|<><Foo></Foo></>|]
+let arrayOfListOfJsx = [|<><Foo /></>;<><Bar /></>|]
+let sameButWithSpaces = [|<></>|]
+let sameButWithSpaces = [|<><Foo /></>|]
+let sameButWithSpaces = [|<><Foo /></>;<><Bar /></>|]
+let sameButWithSpaces = [|<><Foo /></>;<><Bar /></>|]
 let arrayOfJsx = [||]
-let arrayOfJsx = [|((Foo.createElement ~children:[] ())[@JSX ])|]
-let arrayOfJsx =
-  [|((Foo.createElement ~children:[] ())
-    [@JSX ]);((Bar.createElement ~children:[] ())[@JSX ])|]
+let arrayOfJsx = [|<Foo></Foo>|]
+let arrayOfJsx = [|<Foo />;<Bar />|]
 let sameButWithSpaces = [||]
-let sameButWithSpaces = [|((Foo.createElement ~children:[] ())[@JSX ])|]
-let sameButWithSpaces =
-  [|((Foo.createElement ~children:[] ())
-    [@JSX ]);((Bar.createElement ~children:[] ())[@JSX ])|]
-let _ = ((a ~children:[] ())[@JSX ]) < ((b ~children:[] ())[@JSX ])
-let _ = ((a ~children:[] ())[@JSX ]) > ((b ~children:[] ())[@JSX ])
-let _ = ((a ~children:[] ())[@JSX ]) < ((b ~children:[] ())[@JSX ])
-let _ = ((a ~children:[] ())[@JSX ]) > ((b ~children:[] ())[@JSX ])
+let sameButWithSpaces = [|<Foo />|]
+let sameButWithSpaces = [|<Foo />;<Bar />|]
+let _ = <a /> < <b />
+let _ = <a /> > <b />
+let _ = <a></a> < <b></b>
+let _ = <a></a> > <b></b>
 let y =
-  ((Routes.createElement ~path:(Routes.stateToPath state) ~isHistorical:true
-      ~onHashChange:((fun [arity:3]_oldPath ->
-                        fun _oldUrl ->
-                          fun newUrl ->
-                            updater
-                              (fun [arity:2]latestComponentBag ->
-                                 fun _ ->
-                                   ((let currentActualPath =
-                                       Routes.hashOfUri newUrl in
-                                     let pathFromState =
-                                       Routes.stateToPath
-                                         latestComponentBag.state in
-                                     ((if currentActualPath == pathFromState
-                                       then None
-                                       else
-                                         dispatchEventless
-                                           (State.UriNavigated
-                                              currentActualPath)
-                                           latestComponentBag ())
-                                       [@res.ternary ]))
-                                   [@res.braces ])) ())[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
+  <Routes path=(Routes.stateToPath state) isHistorical=true onHashChange=((
+  fun [arity:3]_oldPath ->
+    fun _oldUrl ->
+      fun newUrl ->
+        updater
+          (fun [arity:2]latestComponentBag ->
+             fun _ ->
+               ((let currentActualPath = Routes.hashOfUri newUrl in
+                 let pathFromState =
+                   Routes.stateToPath latestComponentBag.state in
+                 ((if currentActualPath == pathFromState
+                   then None
+                   else
+                     dispatchEventless (State.UriNavigated currentActualPath)
+                       latestComponentBag ())
+                   [@res.ternary ]))
+               [@res.braces ])) ())
+  [@res.braces ]) />
 let z =
-  ((div
-      ~style:(ReactDOMRe.Style.make ~width ~height ~color ~backgroundColor
-                ~margin ~padding ~border ~borderColor ~someOtherAttribute ())
-      ~key:(string_of_int 1) ~children:[] ())
-  [@JSX ])
+  <div style=(ReactDOMRe.Style.make ~width ~height ~color ~backgroundColor
+                ~margin ~padding ~border ~borderColor ~someOtherAttribute ()) key=(
+  string_of_int 1) />
 let omega =
-  ((div
-      ~aList:[width;
+  <div aList=[width;
              height;
              color;
              backgroundColor;
@@ -167,339 +108,235 @@ let omega =
              padding;
              border;
              borderColor;
-             someOtherAttribute] ~key:(string_of_int 1) ~children:[] ())
-  [@JSX ])
+             someOtherAttribute] key=(string_of_int 1) />
 let someArray =
-  ((div
-      ~anArray:[|width;height;color;backgroundColor;margin;padding;border;borderColor;someOtherAttribute|]
-      ~key:(string_of_int 1) ~children:[] ())
-  [@JSX ])
+  <div anArray=[|width;height;color;backgroundColor;margin;padding;border;borderColor;someOtherAttribute|] key=(
+  string_of_int 1) />
 let tuples =
-  ((div
-      ~aTuple:(width, height, color, backgroundColor, margin, padding,
-                border, borderColor, someOtherAttribute, definitelyBreakere)
-      ~key:(string_of_int 1) ~children:[] ())
-  [@JSX ])
+  <div aTuple=(width, height, color, backgroundColor, margin, padding,
+                border, borderColor, someOtherAttribute, definitelyBreakere) key=(
+  string_of_int 1) />
 let icon =
-  ((Icon.createElement
-      ~name:((match state.volume with
-              | v when v < 0.1 -> {js|sound-off|js}
-              | v when v < 0.11 -> {js|sound-min|js}
-              | v when v < 0.51 -> {js|sound-med|js}
-              | _ -> {js|sound-max|js})[@res.braces ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((MessengerSharedPhotosAlbumViewPhotoReact.createElement
-      ?ref:((if foo#bar === baz
-             then Some (foooooooooooooooooooooooo setRefChild)
-             else None)[@res.ternary ]) ~key:(node#legacy_attachment_id)
-      ~children:[] ())
-  [@JSX ])
-let _ = ((Foo.createElement ~bar ~children:[] ())[@JSX ])
-let _ = ((Foo.createElement ?bar ~children:[] ())[@JSX ])
-let _ = ((Foo.createElement ?bar:Baz.bar ~children:[] ())[@JSX ])
-let x = ((div ~children:[] ())[@JSX ])
-let _ = ((div ~asd:1 ~children:[] ())[@JSX ])
-;;foo#bar #= ((bar ~children:[] ())[@JSX ])
-;;foo #= ((bar ~children:[] ())[@JSX ])
-;;foo #= ((bar ~children:[] ())[@JSX ])
-let x = [|((div ~children:[] ())[@JSX ])|]
-let z = ((div ~children:[] ())[@JSX ])
+  <Icon name=((match state.volume with
+               | v when v < 0.1 -> {js|sound-off|js}
+               | v when v < 0.11 -> {js|sound-min|js}
+               | v when v < 0.51 -> {js|sound-med|js}
+               | _ -> {js|sound-max|js})
+  [@res.braces ]) />
+let _ =
+  <MessengerSharedPhotosAlbumViewPhotoReact ref=?((if foo#bar === baz
+                                                   then
+                                                     Some
+                                                       (foooooooooooooooooooooooo
+                                                          setRefChild)
+                                                   else None)
+  [@res.ternary ]) key=(node#legacy_attachment_id) />
+let _ = <Foo bar />
+let _ = <Foo bar=?bar />
+let _ = <Foo bar=?Baz.bar />
+let x = <div />
+let _ = <div asd=1></div>
+;;foo#bar #= <bar />
+;;foo #= <bar />
+;;foo #= <bar />
+let x = [|<div />|]
+let z = <div />
 let z =
-  (((Button.createElement ~onClick:handleStaleClick ~children:[] ())[@JSX ]),
-    ((Button.createElement ~onClick:handleStaleClick ~children:[] ())
-    [@JSX ]))
-let y = [|((div ~children:[] ())[@JSX ]);((div ~children:[] ())[@JSX ])|]
+  (<Button onClick=handleStaleClick />, <Button onClick=handleStaleClick />)
+let y = [|<div />;<div />|]
 let y =
-  [|((Button.createElement ~onClick:handleStaleClick ~children:[] ())
-    [@JSX ]);((Button.createElement ~onClick:handleStaleClick ~children:[] ())
-    [@JSX ])|]
-let _ =
-  ((Description.createElement
-      ~term:((Text.createElement ~text:{js|Age|js} ~children:[] ())
-      [@res.braces ][@JSX ]) ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((Description.createElement
-      ~term:((Text.createElement ~text:{js|Age|js} ~children:[||] ())
-      [@res.braces ]) ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((Description.createElement
-      ~term:((Text.createElement ~text:{js|Age|js} ())[@res.braces ][@JSX ])
-      ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((Description.createElement
-      ~term:((Text.createElement ~superLongPunnedProp
-                ~anotherSuperLongOneCrazyLongThingHere ~text:{js|Age|js}
-                ~children:[] ())[@res.braces ][@JSX ]) ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((Foo.createElement
-      ~bar:((Baz.createElement ~superLongPunnedProp
-               ~anotherSuperLongOneCrazyLongThingHere ~children:[] ())
-      [@res.braces ][@JSX ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div ~children:[((span ~children:[str {js|hello|js}] ())[@JSX ])] ())
-  [@JSX ])
-let _ =
-  ((description ~term:((text ~text:{js|Age|js} ~children:[] ())
-      [@res.braces ][@JSX ]) ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((description ~term:((text ~text:{js|Age|js} ~children:[||] ())
-      [@res.braces ]) ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((description ~term:((text ~text:{js|Age|js} ~children:[||])
-      [@res.braces ][@JSX ]) ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((description ~term:((text ~text:{js|Age|js} ())[@res.braces ][@JSX ])
-      ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((description
-      ~term:((div ~superLongPunnedProp ~anotherSuperLongOneCrazyLongThingHere
-                ~text:{js|Age|js} ~children:[] ())[@res.braces ][@JSX ])
-      ~children:[child] ())
-  [@JSX ])
-let _ =
-  ((div ~onClick:((fun [arity:1]event -> handleChange event)[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div
-      ~onClick:((fun [arity:1]eventWithLongIdent ->
-                   handleChange eventWithLongIdent)[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div
-      ~onClick:((fun [arity:1]event -> ((Js.log event; handleChange event)
-                   [@res.braces ]))[@res.braces ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((StaticDiv.createElement
-      ~onClick:((fun [arity:5]foo ->
-                   fun bar ->
-                     fun baz ->
-                       fun lineBreak ->
-                         fun identifier ->
-                           ((doStuff foo bar baz; bar lineBreak identifier)
-                           [@res.braces ]))[@res.braces ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((AttrDiv.createElement
-      ~onClick:((fun [arity:1]event -> handleChange event)
-      [@res.braces ][@bar ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((AttrDiv.createElement
-      ~onClick:((fun [arity:1]eventLongIdentifier ->
-                   handleChange eventLongIdentifier)[@res.braces ][@bar ])
-      ~children:[] ())
-  [@JSX ])
-let _ =
-  ((StaticDivNamed.createElement
-      ~onClick:((fun [arity:6]~foo ->
-                   fun ~bar ->
-                     fun ~baz ->
-                       fun ~lineBreak ->
-                         fun ~identifier ->
-                           fun () -> bar lineBreak identifier)[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div
-      ~onClick:((fun [arity:1]e -> (((doStuff (); bar foo)
-                   [@res.braces ]) : event))[@res.braces ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div
-      ~onClick:((fun [arity:2]e ->
+  [|<Button onClick=handleStaleClick />;<Button onClick=handleStaleClick />|]
+let _ = <Description term=((<Text text={js|Age|js} />)
+  [@res.braces ])>child</Description>
+let _ =
+  <Description term=((Text.createElement ~text:{js|Age|js} ~children:[||] ())
+  [@res.braces ])>child</Description>
+let _ = <Description term=((Text.createElement ~text:{js|Age|js} ())
+  [@res.braces ][@JSX ])>child</Description>
+let _ =
+  <Description term=((<Text superLongPunnedProp anotherSuperLongOneCrazyLongThingHere text={js|Age|js} />)
+  [@res.braces ])>child</Description>
+let _ =
+  <Foo bar=((<Baz superLongPunnedProp anotherSuperLongOneCrazyLongThingHere />)
+  [@res.braces ]) />
+let _ = <div><span>(str {js|hello|js})</span></div>
+let _ = <description term=((<text text={js|Age|js} />)
+  [@res.braces ])>child</description>
+let _ = <description term=((text ~text:{js|Age|js} ~children:[||] ())
+  [@res.braces ])>child</description>
+let _ = <description term=((text ~text:{js|Age|js} ~children:[||])
+  [@res.braces ][@JSX ])>child</description>
+let _ = <description term=((text ~text:{js|Age|js} ())
+  [@res.braces ][@JSX ])>child</description>
+let _ =
+  <description term=((<div superLongPunnedProp anotherSuperLongOneCrazyLongThingHere text={js|Age|js} />)
+  [@res.braces ])>child</description>
+let _ = <div onClick=((fun [arity:1]event -> handleChange event)
+  [@res.braces ]) />
+let _ =
+  <div onClick=((fun [arity:1]eventWithLongIdent ->
+                   handleChange eventWithLongIdent)
+  [@res.braces ]) />
+let _ =
+  <div onClick=((fun [arity:1]event -> ((Js.log event; handleChange event)
+                   [@res.braces ]))
+  [@res.braces ]) />
+let _ =
+  <StaticDiv onClick=((fun [arity:5]foo ->
+                         fun bar ->
+                           fun baz ->
+                             fun lineBreak ->
+                               fun identifier ->
+                                 ((doStuff foo bar baz;
+                                   bar lineBreak identifier)
+                                 [@res.braces ]))
+  [@res.braces ]) />
+let _ = <AttrDiv onClick=((fun [arity:1]event -> handleChange event)
+  [@res.braces ][@bar ]) />
+let _ =
+  <AttrDiv onClick=((fun [arity:1]eventLongIdentifier ->
+                       handleChange eventLongIdentifier)
+  [@res.braces ][@bar ]) />
+let _ =
+  <StaticDivNamed onClick=((fun [arity:6]~foo ->
+                              fun ~bar ->
+                                fun ~baz ->
+                                  fun ~lineBreak ->
+                                    fun ~identifier ->
+                                      fun () -> bar lineBreak identifier)
+  [@res.braces ]) />
+let _ =
+  <div onClick=((fun [arity:1]e -> (((doStuff (); bar foo)
+                   [@res.braces ]) : event))
+  [@res.braces ]) />
+let _ =
+  <div onClick=((fun [arity:2]e ->
                    fun e2 -> (((doStuff (); bar foo)[@res.braces ]) : event))
-      [@res.braces ]) ~children:[] ())
-  [@JSX ])
+  [@res.braces ]) />
 let _ =
-  ((div
-      ~onClick:((fun [arity:5]foo ->
+  <div onClick=((fun [arity:5]foo ->
                    fun bar ->
                      fun baz ->
                        fun superLongIdent ->
                          fun breakLine -> (((doStuff (); bar foo)
                            [@res.braces ]) : (event * event2 * event3 *
                                                event4 * event5)))
-      [@res.braces ]) ~children:[] ())
-  [@JSX ])
+  [@res.braces ]) />
 let _ =
-  ((div
-      ~onClick:((fun [arity:5]foo ->
+  <div onClick=((fun [arity:5]foo ->
                    fun bar ->
                      fun baz ->
                        fun superLongIdent ->
                          fun breakLine ->
                            (doStuff () : (event * event2 * event3 * event4 *
-                                           event5)))[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
+                                           event5)))
+  [@res.braces ]) />
 let _ =
-  ((div
-      ~children:[(((match color with
-                    | Black -> ReasonReact.string {js|black|js}
-                    | Red -> ReasonReact.string {js|red|js}))
-                [@res.braces ])] ())
-  [@JSX ])
+  <div>((match color with
+         | Black -> ReasonReact.string {js|black|js}
+         | Red -> ReasonReact.string {js|red|js})
+  [@res.braces ])</div>
 let _ =
-  ((div
-      ~style:((ReactDOMRe.Style.make ~width:{js|20px|js} ~height:{js|20px|js}
+  <div style=((ReactDOMRe.Style.make ~width:{js|20px|js} ~height:{js|20px|js}
                  ~borderRadius:{js|100%|js} ~backgroundColor:{js|red|js})
-      [@res.braces ][@foo ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((Animated.createElement ~initialValue:0.0 ~value
-      ~children:((ReactDOMRe.Style.make ~width:{js|20px|js}
-                    ~height:{js|20px|js} ~borderRadius:{js|100%|js}
-                    ~backgroundColor:{js|red|js})[@res.braces ]) ())
-  [@JSX ])
-let _ =
-  ((Animated.createElement ~initialValue:0.0 ~value
-      ~children:((fun [arity:1]value ->
-                    ((div
-                        ~style:((ReactDOMRe.Style.make ~width:{js|20px|js}
-                                   ~height:{js|20px|js}
-                                   ~borderRadius:{js|100%|js}
-                                   ~backgroundColor:{js|red|js})
-                        [@res.braces ]) ~children:[] ())
-                    [@JSX ]))[@res.braces ]) ())
-  [@JSX ])
-let _ =
-  ((Animated.createElement ~initialValue:0.0 ~value
-      ~children:((fun [arity:1]value ->
-                    (((div
-                         ~style:((ReactDOMRe.Style.make ~width:{js|20px|js}
-                                    ~height:{js|20px|js}
-                                    ~borderRadius:{js|100%|js}
-                                    ~backgroundColor:{js|red|js})
-                         [@res.braces ]) ~children:[] ())
-                    [@JSX ]) : ReasonReact.element))[@res.braces ]) ())
-  [@JSX ])
-let _ =
-  ((Animated.createElement ~initialValue:0.0 ~value
-      ~children:((fun [arity:1]value ->
-                    ((div
-                        ~style:((ReactDOMRe.Style.make ~width:{js|20px|js}
-                                   ~height:{js|20px|js}
-                                   ~borderRadius:{js|100%|js}
-                                   ~backgroundColor:{js|red|js})
-                        [@res.braces ]) ~children:[] ())
-                    [@res.braces ][@JSX ]))[@res.braces ][@foo ]) ())
-  [@JSX ])
-let _ =
-  ((Animated.createElement ~initialValue:0.0 ~value
-      ~children:((fun [arity:1]value ->
-                    ((let width = {js|20px|js} in
-                      let height = {js|20px|js} in
-                      ((div
-                          ~style:((ReactDOMRe.Style.make ~width ~height
-                                     ~borderRadius:{js|100%|js}
-                                     ~backgroundColor:{js|red|js})
-                          [@res.braces ]) ~children:[] ())
-                        [@JSX ]))
-                    [@res.braces ]))[@res.braces ]) ())
-  [@JSX ])
-let _ =
-  ((div ~callback:((reduce (fun [arity:1]() -> not state))[@res.braces ])
-      ~children:[] ())
-  [@JSX ])
-let _ =
-  ((button ?id ~className:((Cn.make [|{js|button|js};{js|is-fullwidth|js}|])
-      [@res.braces ]) ~onClick
-      ~children:[((ste {js|Submit|js})[@res.braces ])] ())
-  [@JSX ])
-let _ =
-  ((button ?id ~className:((Cn.make [{js|button|js}; {js|is-fullwidth|js}])
-      [@res.braces ]) ~onClick
-      ~children:[((ste {js|Submit|js})[@res.braces ])] ())
-  [@JSX ])
-let _ =
-  ((button ?id ~className:((Cn.make ({js|button|js}, {js|is-fullwidth|js}))
-      [@res.braces ]) ~onClick
-      ~children:[((ste {js|Submit|js})[@res.braces ])] ())
-  [@JSX ])
-let _ =
-  ((button ?id ~className:((Cn.make { a = b })[@res.braces ]) ~onClick
-      ~children:[((ste {js|Submit|js})[@res.braces ])] ())
-  [@JSX ])
-let _ =
-  ((X.createElement ~y:((z -> (Belt.Option.getWithDefault {js||js}))
-      [@res.braces ]) ~children:[] ())
-  [@JSX ])
-let _ =
-  ((div ~style:((getStyle ())[@res.braces ])
-      ~children:[((ReasonReact.string {js|BugTest|js})[@res.braces ])] ())
-  [@JSX ])
-let _ =
-  ((div
-      ~children:[(((let left = limit -> Int.toString in
-                    (((((({js||js})[@res.template ]) ++ left)
-                        [@res.template ]) ++ (({js| characters left|js})
-                        [@res.template ]))
-                      [@res.template ]) -> React.string))
-                [@res.braces ])] ())
-  [@JSX ])
-let _ =
-  ((View.createElement ~style:(styles#backgroundImageWrapper)
-      ~children:[(((let uri = {js|/images/header-background.png|js} in
-                    ((Image.createElement ~resizeMode:Contain
-                        ~style:(styles#backgroundImage) ~uri ~children:[] ())
-                      [@JSX ])))
-                [@res.braces ])] ())
-  [@JSX ])
-;;((div
-      ~children:[((ReasonReact.array
-                     (Array.of_list
-                        (List.map
-                           (fun [arity:1]possibleGradeValue ->
-                              ((option
-                                  ~key:((string_of_int possibleGradeValue)
-                                  [@res.braces ])
-                                  ~value:((string_of_int possibleGradeValue)
-                                  [@res.braces ])
-                                  ~children:[((str
-                                                 (string_of_int
-                                                    possibleGradeValue))
-                                            [@res.braces ])] ())
-                              [@JSX ]))
-                           (List.filter
-                              (fun [arity:1]g -> g <= state.maxGrade)
-                              possibleGradeValues))))
-                [@res.braces ])] ())[@JSX ])
-;;((div ~children:[((Js.log (a <= 10))[@res.braces ])] ())[@JSX ])
-;;((div
-      ~children:[((div ~children:[((Js.log (a <= 10))[@res.braces ])] ())
-                [@JSX ])] ())[@JSX ])
-;;((div
-      ~children:[((div ~onClick:((fun [arity:1]_ -> Js.log (a <= 10))
-                     [@res.braces ])
-                     ~children:[((div
-                                    ~children:[((Js.log (a <= 10))
-                                              [@res.braces ])] ())
-                               [@JSX ])] ())
-                [@JSX ])] ())[@JSX ])
-;;((div ~children:element ())[@JSX ])
-;;((div ~children:((fun [arity:1]a -> 1)[@res.braces ]) ())[@JSX ])
-;;((div ~children:((span ~children:[] ())[@JSX ]) ())[@JSX ])
-;;((div ~children:[|a|] ())[@JSX ])
-;;((div ~children:(1, 2) ())[@JSX ])
-;;((div ~children:((array -> f)[@res.braces ]) ())[@JSX ])
-;;(([element])[@JSX ])
-;;(([(((fun [arity:1]a -> 1))[@res.braces ])])[@JSX ])
-;;(([((span ~children:[] ())[@JSX ])])[@JSX ])
-;;(([[|a|]])[@JSX ])
-;;(([(1, 2)])[@JSX ])
-;;(([((array -> f)[@res.braces ])])[@JSX ])
-let _ = ((A.createElement ~x:{js|y|js} ~_spreadProps:str ~children:[] ())
-  [@JSX ])
\ No newline at end of file
+  [@res.braces ][@foo ]) />
+let _ =
+  <Animated initialValue=0.0 value>((ReactDOMRe.Style.make
+                                       ~width:{js|20px|js}
+                                       ~height:{js|20px|js}
+                                       ~borderRadius:{js|100%|js}
+                                       ~backgroundColor:{js|red|js})
+  [@res.braces ])</Animated>
+let _ =
+  <Animated initialValue=0.0 value>((fun [arity:1]value ->
+                                       <div style=((ReactDOMRe.Style.make
+                                                      ~width:{js|20px|js}
+                                                      ~height:{js|20px|js}
+                                                      ~borderRadius:{js|100%|js}
+                                                      ~backgroundColor:{js|red|js})
+                                       [@res.braces ]) />)
+  [@res.braces ])</Animated>
+let _ =
+  <Animated initialValue=0.0 value>((fun [arity:1]value ->
+                                       (<div style=((ReactDOMRe.Style.make
+                                                       ~width:{js|20px|js}
+                                                       ~height:{js|20px|js}
+                                                       ~borderRadius:{js|100%|js}
+                                                       ~backgroundColor:{js|red|js})
+                                       [@res.braces ]) /> : ReasonReact.element))
+  [@res.braces ])</Animated>
+let _ =
+  <Animated initialValue=0.0 value>((fun [arity:1]value ->
+                                       ((<div style=((ReactDOMRe.Style.make
+                                                        ~width:{js|20px|js}
+                                                        ~height:{js|20px|js}
+                                                        ~borderRadius:{js|100%|js}
+                                                        ~backgroundColor:{js|red|js})
+                                       [@res.braces ]) />)[@res.braces ]))
+  [@res.braces ][@foo ])</Animated>
+let _ =
+  <Animated initialValue=0.0 value>((fun [arity:1]value ->
+                                       ((let width = {js|20px|js} in
+                                         let height = {js|20px|js} in
+                                         <div style=((ReactDOMRe.Style.make
+                                                        ~width ~height
+                                                        ~borderRadius:{js|100%|js}
+                                                        ~backgroundColor:{js|red|js})
+                                           [@res.braces ]) />)
+                                       [@res.braces ]))
+  [@res.braces ])</Animated>
+let _ = <div callback=((reduce (fun [arity:1]() -> not state))
+  [@res.braces ]) />
+let _ =
+  <button ?id className=((Cn.make [|{js|button|js};{js|is-fullwidth|js}|])
+  [@res.braces ]) onClick>((ste {js|Submit|js})[@res.braces ])</button>
+let _ =
+  <button ?id className=((Cn.make [{js|button|js}; {js|is-fullwidth|js}])
+  [@res.braces ]) onClick>((ste {js|Submit|js})[@res.braces ])</button>
+let _ =
+  <button ?id className=((Cn.make ({js|button|js}, {js|is-fullwidth|js}))
+  [@res.braces ]) onClick>((ste {js|Submit|js})[@res.braces ])</button>
+let _ = <button ?id className=((Cn.make { a = b })
+  [@res.braces ]) onClick>((ste {js|Submit|js})[@res.braces ])</button>
+let _ = <X y=((z -> (Belt.Option.getWithDefault {js||js}))[@res.braces ]) />
+let _ = <div style=((getStyle ())
+  [@res.braces ])>((ReasonReact.string {js|BugTest|js})[@res.braces ])</div>
+let _ =
+  <div>((let left = limit -> Int.toString in
+         (((((({js||js})[@res.template ]) ++ left)[@res.template ]) ++
+             (({js| characters left|js})[@res.template ]))
+           [@res.template ]) -> React.string)
+  [@res.braces ])</div>
+let _ =
+  <View style=(styles#backgroundImageWrapper)>((let uri =
+                                                  {js|/images/header-background.png|js} in
+                                                <Image resizeMode=Contain style=(
+                                                  styles#backgroundImage) uri />)
+  [@res.braces ])</View>
+;;<div>((ReasonReact.array
+           (Array.of_list
+              (List.map
+                 (fun [arity:1]possibleGradeValue ->
+                    <option key=((string_of_int possibleGradeValue)
+                    [@res.braces ]) value=((string_of_int possibleGradeValue)
+                    [@res.braces ])>((str (string_of_int possibleGradeValue))
+                    [@res.braces ])</option>)
+                 (List.filter (fun [arity:1]g -> g <= state.maxGrade)
+                    possibleGradeValues))))[@res.braces ])</div>
+;;<div>((Js.log (a <= 10))[@res.braces ])</div>
+;;<div><div>((Js.log (a <= 10))[@res.braces ])</div></div>
+;;<div><div onClick=((fun [arity:1]_ -> Js.log (a <= 10))
+  [@res.braces ])><div>((Js.log (a <= 10))[@res.braces ])</div></div></div>
+;;<div>element</div>
+;;<div>((fun [arity:1]a -> 1)[@res.braces ])</div>
+;;<div><span /></div>
+;;<div>[|a|]</div>
+;;<div>(1, 2)</div>
+;;<div>((array -> f)[@res.braces ])</div>
+;;<>element</>
+;;<>((fun [arity:1]a -> 1)[@res.braces ])</>
+;;<><span /></>
+;;<>[|a|]</>
+;;<>(1, 2)</>
+;;<>((array -> f)[@res.braces ])</>
+let _ = <A x={js|y|js} {...str} />
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/parenthesized.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/parenthesized.res.txt
index 9640bcb5fe..6c21a3bff9 100644
--- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/parenthesized.res.txt
+++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/parenthesized.res.txt
@@ -16,7 +16,7 @@ let aTuple = (1, 2)
 let aRecord = { name = {js|steve|js}; age = 30 }
 let blockExpression = ((let a = 1 in let b = 2 in a + b)[@res.braces ])
 let assertSmthing = assert true
-let jsx = ((div ~className:{js|cx|js} ~children:[foo] ())[@JSX ])
+let jsx = <div className={js|cx|js}>foo</div>
 let ifExpr = if true then Js.log true else Js.log false
 let forExpr = for p = 0 to 10 do () done
 let whileExpr = while true do doSomeImperativeThing () done
diff --git a/tests/syntax_tests/data/parsing/infiniteLoops/expected/jsxChildren.res.txt b/tests/syntax_tests/data/parsing/infiniteLoops/expected/jsxChildren.res.txt
index 2aedc083a2..bc2fbd3a60 100644
--- a/tests/syntax_tests/data/parsing/infiniteLoops/expected/jsxChildren.res.txt
+++ b/tests/syntax_tests/data/parsing/infiniteLoops/expected/jsxChildren.res.txt
@@ -20,7 +20,7 @@
 
 type nonrec action =
   | AddUser 
-;;((string ~children:[] ())[@JSX ])
+;;<string></string>
 let (a : action) = AddUser {js|test|js}
 ;;etype
 ;;s = { x = ((list < i) > ([%rescript.exprhole ])) }
\ No newline at end of file
diff --git a/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt b/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt
index 80c3bb1d86..225a99a4c0 100644
--- a/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt
+++ b/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt
@@ -1,14 +1,29 @@
 @@jsxConfig({version: 4})
 
 let _ = React.jsx(React.jsxFragment, {})
-let _ = React.jsx(React.jsxFragment, {children: ReactDOM.jsx("div", {})})
+let _ = React.jsx(
+  React.jsxFragment,
+  {
+    children: ReactDOM.jsx("div", {}),
+  },
+)
 let _ = React.jsxs(
   React.jsxFragment,
   {children: React.array([ReactDOM.jsx("div", {}), ReactDOM.jsx("div", {})])},
 )
-let _ = React.jsx(React.jsxFragment, {children: React.jsx(React.jsxFragment, {})})
+let _ = React.jsx(
+  React.jsxFragment,
+  {
+    children: React.jsx(React.jsxFragment, {}),
+  },
+)
 let _ = React.jsx(Z.make, {})
-let _ = React.jsx(Z.make, {children: ReactDOM.jsx("div", {})})
+let _ = React.jsx(
+  Z.make,
+  {
+    children: ReactDOM.jsx("div", {}),
+  },
+)
 let _ = React.jsx(Z.make, {a: "a", children: ReactDOM.jsx("div", {})})
 let _ = React.jsxs(
   Z.make,
diff --git a/tests/syntax_tests/data/printer/expr/expected/braced.res.txt b/tests/syntax_tests/data/printer/expr/expected/braced.res.txt
index 26c9d855b4..80ea824389 100644
--- a/tests/syntax_tests/data/printer/expr/expected/braced.res.txt
+++ b/tests/syntax_tests/data/printer/expr/expected/braced.res.txt
@@ -318,5 +318,5 @@ let x = {
 }
 
 // string constant should be printed correct
-(<> {"\n"->React.string} </>)
-(<> {"\""->React.string} </>)
+<> {"\n"->React.string} </>
+<> {"\""->React.string} </>
diff --git a/tests/syntax_tests/data/printer/expr/expected/jsx.res.txt b/tests/syntax_tests/data/printer/expr/expected/jsx.res.txt
index 5df8ca4e5a..3e1e225912 100644
--- a/tests/syntax_tests/data/printer/expr/expected/jsx.res.txt
+++ b/tests/syntax_tests/data/printer/expr/expected/jsx.res.txt
@@ -333,7 +333,9 @@ module App = {
   amount={
     // ->BN.new_("86400")
     // There are 86400 seconds in a day.
-    amount->BN.div(BN.new_("86400"))->BN.toString
+    amount
+    ->BN.div(BN.new_("86400"))
+    ->BN.toString
   }
 />
 
@@ -435,7 +437,7 @@ let x = <C> ...{() => msg->React.string} </C>
 
 let x = <C> ...{array->Array.map(React.string)} </C>
 
-let x = <> {array->Array.map(React.string)} </>
+let x = <> ...{array->Array.map(React.string)} </>
 
 let x = {
   let _ = <div />
@@ -525,3 +527,44 @@ let x =
       }}
     />
   </div>
+
+let moo =
+  <div>
+    <p> {React.string("moo")} </p>
+    // c1
+    <p> {React.string("moo")} </p>
+    // c2
+    // c3
+    <p> {React.string("moo")} </p>
+
+    // c4
+
+    <p> {React.string("moo")} </p>
+  </div>
+
+let fragmented_moo =
+  <>
+    <p> {React.string("moo")} </p>
+    // c1
+    <p> {React.string("moo")} </p>
+    // c2
+    // c3
+    <p> {React.string("moo")} </p>
+
+    // c4
+
+    <p> {React.string("moo")} </p>
+  </>
+
+let arrow_with_fragment = el => <>
+  {t(nbsp ++ "(")}
+  el
+  {t(")")}
+</>
+
+let arrow_with_container_tag = el =>
+  <div>
+    {t(nbsp ++ "(")}
+    el
+    {t(")")}
+  </div>
diff --git a/tests/syntax_tests/data/printer/expr/expected/switch.res.txt b/tests/syntax_tests/data/printer/expr/expected/switch.res.txt
index da576e4d29..acfb416794 100644
--- a/tests/syntax_tests/data/printer/expr/expected/switch.res.txt
+++ b/tests/syntax_tests/data/printer/expr/expected/switch.res.txt
@@ -43,14 +43,12 @@ switch count {
 
 switch route {
 | A =>
-  <div>
-    // div tag moves to the next line
+  <div> // div tag stays after >
     <div> {React.string("First A div")} </div>
     <div> {React.string("Second A div")} </div>
   </div>
 | B =>
-  <>
-    // fragment tag moves to the next line
+  <> // fragment tag stays after <>
     <div> {React.string("First B div")} </div>
     <div> {React.string("Second B div")} </div>
   </>
diff --git a/tests/syntax_tests/data/printer/expr/jsx.res b/tests/syntax_tests/data/printer/expr/jsx.res
index f34a3e5483..6a3c54cbb4 100644
--- a/tests/syntax_tests/data/printer/expr/jsx.res
+++ b/tests/syntax_tests/data/printer/expr/jsx.res
@@ -509,3 +509,41 @@ let x =
       }}
     />
   </div>
+
+let moo =
+  <div>
+    <p> {React.string("moo")} </p>
+    // c1
+    <p> {React.string("moo")} </p>
+    // c2
+    // c3
+    <p> {React.string("moo")} </p>
+    // c4
+
+    <p> {React.string("moo")} </p>
+  </div>
+
+let fragmented_moo =
+  <>
+    <p> {React.string("moo")} </p>
+    // c1
+    <p> {React.string("moo")} </p>
+    // c2
+    // c3
+    <p> {React.string("moo")} </p>
+    // c4
+
+    <p> {React.string("moo")} </p>
+  </>
+
+let arrow_with_fragment = el => <>
+  {t(nbsp ++ "(")}
+  el
+  {t(")")}
+</>
+
+let arrow_with_container_tag = el => <div>
+  {t(nbsp ++ "(")}
+  el
+  {t(")")}
+</div>
\ No newline at end of file
diff --git a/tests/syntax_tests/data/printer/expr/switch.res b/tests/syntax_tests/data/printer/expr/switch.res
index 7d4f8aa0f6..107a9e6d4c 100644
--- a/tests/syntax_tests/data/printer/expr/switch.res
+++ b/tests/syntax_tests/data/printer/expr/switch.res
@@ -40,11 +40,11 @@ switch count {
 }
 
 switch route {
-| A => <div> // div tag moves to the next line
+| A => <div> // div tag stays after >
     <div> {React.string("First A div")} </div>
     <div> {React.string("Second A div")} </div>
   </div>
-| B => <> // fragment tag moves to the next line
+| B => <> // fragment tag stays after <>
     <div> {React.string("First B div")} </div>
     <div> {React.string("Second B div")} </div>
   </>