or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-react.mdcss-styling.mddom-integration.mderror-boundaries.mdevent-system.mdindex.mdreact-hooks.mdrouting.mdserver-side-rendering.mdtesting-utilities.md
tile.json

dom-integration.mddocs/

DOM Integration

Comprehensive DOM operations including rendering, hydration, portals, and complete props system supporting all HTML, SVG, and accessibility attributes.

Core DOM Functions

/* DOM utilities */
[@bs.val] [@bs.return nullable]
external querySelector: string => option(Dom.element) = "document.querySelector";

/* Rendering */
[@bs.module "react-dom"]
external render: (React.element, Dom.element) => unit = "render";

[@bs.module "react-dom"]
external hydrate: (React.element, Dom.element) => unit = "hydrate";

[@bs.module "react-dom"]
external unmountComponentAtNode: Dom.element => unit = "unmountComponentAtNode";

/* Portals */
[@bs.module "react-dom"]
external createPortal: (React.element, Dom.element) => React.element = "createPortal";

/* Utilities */
external domElementToObj: Dom.element => Js.t({..}) = "%identity";

Usage Examples:

/* Basic rendering */
let root = ReactDOM.querySelector("#root");
switch (root) {
| Some(element) => ReactDOM.render(<App />, element)
| None => Js.log("Root element not found")
}

/* Server-side rendering hydration */
let container = ReactDOM.querySelector("#app");
switch (container) {
| Some(element) => ReactDOM.hydrate(<App />, element)
| None => ()
}

/* Creating portals */
[@react.component]
let make = (~children) => {
  let modalRoot = ReactDOM.querySelector("#modal-root");
  
  switch (modalRoot) {
  | Some(element) => ReactDOM.createPortal(children, element)
  | None => React.null
  }
}

Experimental Concurrent Features

module Experimental = {
  type root;

  [@bs.module "react-dom"]
  external createRoot: Dom.element => root = "createRoot";

  [@bs.module "react-dom"]
  external createBlockingRoot: Dom.element => root = "createBlockingRoot";

  [@bs.send] 
  external render: (root, React.element) => unit = "render";
};

Usage Example:

let container = ReactDOM.querySelector("#root");
switch (container) {
| Some(element) => {
    let root = ReactDOM.Experimental.createRoot(element);
    ReactDOM.Experimental.render(root, <App />);
  }
| None => ()
}

Reference Types

type domRef;
type style = ReactDOMStyle.t;

module Ref = {
  type t = domRef;
  type currentDomRef = React.ref(Js.nullable(Dom.element));
  type callbackDomRef = Js.nullable(Dom.element) => unit;

  external domRef: currentDomRef => domRef = "%identity";
  external callbackDomRef: callbackDomRef => domRef = "%identity";
};

Props System

DOM Props Type

The domProps type includes comprehensive support for all HTML, SVG, and ARIA attributes:

module Props = {
  [@bs.deriving abstract]
  type domProps = {
    /* Core attributes */
    [@bs.optional] key: string,
    [@bs.optional] ref: domRef,
    [@bs.optional] className: string,
    [@bs.optional] id: string,
    [@bs.optional] style: style,
    
    /* Content and text */
    [@bs.optional] children: React.element,
    [@bs.optional] dangerouslySetInnerHTML: {. "__html": string},
    
    /* Form attributes */
    [@bs.optional] value: string,
    [@bs.optional] defaultValue: string,
    [@bs.optional] checked: bool,
    [@bs.optional] defaultChecked: bool,
    [@bs.optional] placeholder: string,
    [@bs.optional] disabled: bool,
    [@bs.optional] readOnly: bool,
    [@bs.optional] required: bool,
    [@bs.optional] autoFocus: bool,
    [@bs.optional] autoComplete: string,
    [@bs.optional] name: string,
    [@bs.optional] type_: string,
    [@bs.optional] accept: string,
    [@bs.optional] multiple: bool,
    [@bs.optional] min: string,
    [@bs.optional] max: string,
    [@bs.optional] step: string,
    [@bs.optional] pattern: string,
    [@bs.optional] minLength: int,
    [@bs.optional] maxLength: int,
    [@bs.optional] size: int,
    [@bs.optional] rows: int,
    [@bs.optional] cols: int,
    [@bs.optional] wrap: string,
    
    /* Link and media attributes */
    [@bs.optional] href: string,
    [@bs.optional] target: string,
    [@bs.optional] rel: string,
    [@bs.optional] download: string,
    [@bs.optional] src: string,
    [@bs.optional] srcSet: string,
    [@bs.optional] alt: string,
    [@bs.optional] width: string,
    [@bs.optional] height: string,
    [@bs.optional] poster: string,
    [@bs.optional] preload: string,
    [@bs.optional] autoPlay: bool,
    [@bs.optional] controls: bool,
    [@bs.optional] loop: bool,
    [@bs.optional] muted: bool,
    
    /* Layout and styling */
    [@bs.optional] hidden: bool,
    [@bs.optional] title: string,
    [@bs.optional] lang: string,
    [@bs.optional] dir: string,
    [@bs.optional] tabIndex: int,
    [@bs.optional] role: string,
    [@bs.optional] draggable: bool,
    [@bs.optional] contentEditable: bool,
    [@bs.optional] spellCheck: bool,
    [@bs.optional] translate: string,
    
    /* Data attributes */
    [@bs.optional] [@bs.as "data-testid"] dataTestId: string,
    
    /* Event handlers */
    [@bs.optional] onClick: ReactEvent.Mouse.t => unit,
    [@bs.optional] onDoubleClick: ReactEvent.Mouse.t => unit,
    [@bs.optional] onMouseDown: ReactEvent.Mouse.t => unit,
    [@bs.optional] onMouseUp: ReactEvent.Mouse.t => unit,
    [@bs.optional] onMouseEnter: ReactEvent.Mouse.t => unit,
    [@bs.optional] onMouseLeave: ReactEvent.Mouse.t => unit,
    [@bs.optional] onMouseMove: ReactEvent.Mouse.t => unit,
    [@bs.optional] onMouseOut: ReactEvent.Mouse.t => unit,
    [@bs.optional] onMouseOver: ReactEvent.Mouse.t => unit,
    
    [@bs.optional] onChange: ReactEvent.Form.t => unit,
    [@bs.optional] onInput: ReactEvent.Form.t => unit,
    [@bs.optional] onSubmit: ReactEvent.Form.t => unit,
    [@bs.optional] onReset: ReactEvent.Form.t => unit,
    [@bs.optional] onInvalid: ReactEvent.Form.t => unit,
    
    [@bs.optional] onKeyDown: ReactEvent.Keyboard.t => unit,
    [@bs.optional] onKeyPress: ReactEvent.Keyboard.t => unit,
    [@bs.optional] onKeyUp: ReactEvent.Keyboard.t => unit,
    
    [@bs.optional] onFocus: ReactEvent.Focus.t => unit,
    [@bs.optional] onBlur: ReactEvent.Focus.t => unit,
    
    [@bs.optional] onScroll: ReactEvent.UI.t => unit,
    [@bs.optional] onWheel: ReactEvent.Wheel.t => unit,
    
    [@bs.optional] onDrag: ReactEvent.Drag.t => unit,
    [@bs.optional] onDragEnd: ReactEvent.Drag.t => unit,
    [@bs.optional] onDragEnter: ReactEvent.Drag.t => unit,
    [@bs.optional] onDragExit: ReactEvent.Drag.t => unit,
    [@bs.optional] onDragLeave: ReactEvent.Drag.t => unit,
    [@bs.optional] onDragOver: ReactEvent.Drag.t => unit,
    [@bs.optional] onDragStart: ReactEvent.Drag.t => unit,
    [@bs.optional] onDrop: ReactEvent.Drag.t => unit,
    
    [@bs.optional] onTouchCancel: ReactEvent.Touch.t => unit,
    [@bs.optional] onTouchEnd: ReactEvent.Touch.t => unit,
    [@bs.optional] onTouchMove: ReactEvent.Touch.t => unit,
    [@bs.optional] onTouchStart: ReactEvent.Touch.t => unit,
    
    [@bs.optional] onPointerDown: ReactEvent.Pointer.t => unit,
    [@bs.optional] onPointerMove: ReactEvent.Pointer.t => unit,
    [@bs.optional] onPointerUp: ReactEvent.Pointer.t => unit,
    [@bs.optional] onPointerCancel: ReactEvent.Pointer.t => unit,
    [@bs.optional] onPointerEnter: ReactEvent.Pointer.t => unit,
    [@bs.optional] onPointerLeave: ReactEvent.Pointer.t => unit,
    [@bs.optional] onPointerOver: ReactEvent.Pointer.t => unit,
    [@bs.optional] onPointerOut: ReactEvent.Pointer.t => unit,
    
    [@bs.optional] onCopy: ReactEvent.Clipboard.t => unit,
    [@bs.optional] onCut: ReactEvent.Clipboard.t => unit,
    [@bs.optional] onPaste: ReactEvent.Clipboard.t => unit,
    
    [@bs.optional] onCompositionEnd: ReactEvent.Composition.t => unit,
    [@bs.optional] onCompositionStart: ReactEvent.Composition.t => unit,
    [@bs.optional] onCompositionUpdate: ReactEvent.Composition.t => unit,
    
    [@bs.optional] onSelect: ReactEvent.Selection.t => unit,
    
    [@bs.optional] onLoad: ReactEvent.Image.t => unit,
    [@bs.optional] onError: ReactEvent.Image.t => unit,
    
    [@bs.optional] onAnimationStart: ReactEvent.Animation.t => unit,
    [@bs.optional] onAnimationEnd: ReactEvent.Animation.t => unit,
    [@bs.optional] onAnimationIteration: ReactEvent.Animation.t => unit,
    
    [@bs.optional] onTransitionEnd: ReactEvent.Transition.t => unit,
  };
  
  /* Alternative props type with callback ref */
  [@bs.deriving abstract]
  type props = {
    /* Same as domProps but with callback ref */
    [@bs.optional] ref: Js.nullable(Dom.element) => unit,
    /* ... all other props same as domProps */
  };
};

ARIA Attributes

Complete ARIA accessibility attribute support:

/* ARIA attributes (partial list - see domProps for complete set) */
[@bs.optional] [@bs.as "aria-label"] ariaLabel: string,
[@bs.optional] [@bs.as "aria-labelledby"] ariaLabelledby: string,
[@bs.optional] [@bs.as "aria-describedby"] ariaDescribedby: string,
[@bs.optional] [@bs.as "aria-hidden"] ariaHidden: bool,
[@bs.optional] [@bs.as "aria-expanded"] ariaExpanded: bool,
[@bs.optional] [@bs.as "aria-checked"] ariaChecked: string,
[@bs.optional] [@bs.as "aria-selected"] ariaSelected: bool,
[@bs.optional] [@bs.as "aria-disabled"] ariaDisabled: bool,
[@bs.optional] [@bs.as "aria-pressed"] ariaPressed: string,
[@bs.optional] [@bs.as "aria-current"] ariaCurrent: string,
[@bs.optional] [@bs.as "aria-live"] ariaLive: string,
[@bs.optional] [@bs.as "aria-atomic"] ariaAtomic: bool,
[@bs.optional] [@bs.as "aria-busy"] ariaBusy: bool,
[@bs.optional] [@bs.as "aria-relevant"] ariaRelevant: string,

SVG Attributes

Complete SVG attribute support:

/* SVG attributes (partial list) */
[@bs.optional] fill: string,
[@bs.optional] stroke: string,
[@bs.optional] strokeWidth: string,
[@bs.optional] strokeDasharray: string,
[@bs.optional] strokeDashoffset: string,
[@bs.optional] strokeLinecap: string,
[@bs.optional] strokeLinejoin: string,
[@bs.optional] opacity: string,
[@bs.optional] fillOpacity: string,
[@bs.optional] strokeOpacity: string,
[@bs.optional] transform: string,
[@bs.optional] viewBox: string,
[@bs.optional] preserveAspectRatio: string,
[@bs.optional] x: string,
[@bs.optional] y: string,
[@bs.optional] x1: string,
[@bs.optional] y1: string,
[@bs.optional] x2: string,
[@bs.optional] y2: string,
[@bs.optional] cx: string,
[@bs.optional] cy: string,
[@bs.optional] r: string,
[@bs.optional] rx: string,
[@bs.optional] ry: string,
[@bs.optional] d: string,
[@bs.optional] points: string,

DOM Element Creation

/* String-based DOM element creation */
external stringToComponent: string => React.component(domProps) = "%identity";

/* Create DOM elements with props */
external createDOMElementVariadic: (string, ~props: domProps=?, array(React.element)) => React.element = "createElement";

Usage Examples

Form Handling

[@react.component]
let make = () => {
  let (name, setName) = React.useState(() => "");
  let (email, setEmail) = React.useState(() => "");
  let (isSubmitting, setIsSubmitting) = React.useState(() => false);
  
  let handleSubmit = (event: ReactEvent.Form.t) => {
    ReactEvent.Form.preventDefault(event);
    setIsSubmitting(_ => true);
    
    submitForm(name, email)
    |> Promise.then_(_ => {
         setIsSubmitting(_ => false);
         Promise.resolve();
       })
    |> ignore;
  };
  
  <form onSubmit=handleSubmit>
    <div>
      <label htmlFor="name" ariaLabel="Full name">
        {React.string("Name:")}
      </label>
      <input
        id="name"
        type_="text"
        value=name
        onChange={(event) => {
          let value = ReactEvent.Form.target(event)##value;
          setName(_ => value);
        }}
        required=true
        autoComplete="name"
        placeholder="Enter your full name"
      />
    </div>
    
    <div>
      <label htmlFor="email" ariaLabel="Email address">
        {React.string("Email:")}
      </label>
      <input
        id="email"
        type_="email"
        value=email
        onChange={(event) => {
          let value = ReactEvent.Form.target(event)##value;
          setEmail(_ => value);
        }}
        required=true
        autoComplete="email"
        placeholder="Enter your email"
      />
    </div>
    
    <button 
      type_="submit" 
      disabled=isSubmitting
      ariaLabel="Submit form"
    >
      {React.string(isSubmitting ? "Submitting..." : "Submit")}
    </button>
  </form>
}

Accessibility Features

[@react.component]
let make = (~isOpen: bool, ~onClose: unit => unit) => {
  let dialogRef = React.useRef(Js.Nullable.null);
  
  React.useEffect1(() => {
    if (isOpen) {
      switch (dialogRef.current->Js.Nullable.toOption) {
      | Some(element) => element->focus
      | None => ()
      };
    };
    None;
  }, [|isOpen|]);
  
  if (isOpen) {
    ReactDOM.createPortal(
      <div
        className="modal-overlay"
        role="dialog"
        ariaModal=true
        ariaLabelledby="dialog-title"
        ariaDescribedby="dialog-description"
        ref={ReactDOM.Ref.domRef(dialogRef)}
        tabIndex=(-1)
        onClick={(event) => {
          if (ReactEvent.Mouse.target(event) === ReactEvent.Mouse.currentTarget(event)) {
            onClose();
          }
        }}
      >
        <div className="modal-content">
          <h2 id="dialog-title">
            {React.string("Confirmation")}
          </h2>
          <p id="dialog-description">
            {React.string("Are you sure you want to continue?")}
          </p>
          <div>
            <button onClick={_ => onClose()} autoFocus=true>
              {React.string("Cancel")}
            </button>
            <button onClick={_ => onClose()}>
              {React.string("Confirm")}
            </button>
          </div>
        </div>
      </div>,
      querySelector("#modal-root")->Option.getWithDefault(Dom.document##body)
    )
  } else {
    React.null
  }
}

Custom Ref Usage

[@react.component]
let make = () => {
  let inputRef = React.useRef(Js.Nullable.null);
  let (measurements, setMeasurements) = React.useState(() => None);
  
  let measureInput = () => {
    switch (inputRef.current->Js.Nullable.toOption) {
    | Some(element) => {
        let rect = element##getBoundingClientRect();
        setMeasurements(_ => Some({
          width: rect##width,
          height: rect##height,
        }));
      }
    | None => ()
    }
  };
  
  <div>
    <input
      ref={ReactDOM.Ref.domRef(inputRef)}
      placeholder="Measure me"
      onFocus={_ => measureInput()}
    />
    <button onClick={_ => measureInput()}>
      {React.string("Get Measurements")}
    </button>
    {switch (measurements) {
     | Some({width, height}) => 
       <p> {React.string(j`Width: $width, Height: $height`)} </p>
     | None => React.null
     }}
  </div>
}