Skip to main content
Creates a virtual node (VNode) representing a DOM element or component. This is Preact’s React-compatible API for creating virtual DOM nodes.

Signature

// For HTML elements
function createElement<P extends HTMLAttributes<T>, T extends HTMLElement>(
  type: keyof JSXInternal.IntrinsicElements,
  props: (ClassAttributes<T> & P) | null,
  ...children: ComponentChildren[]
): VNode<ClassAttributes<T> & P>

// For components
function createElement<P>(
  type: ComponentType<P> | string,
  props: (Attributes & P) | null,
  ...children: ComponentChildren[]
): VNode<P>

Parameters

type
string | ComponentType<P>
required
The element type. Can be:
  • A string for HTML/SVG elements (e.g., 'div', 'span', 'svg')
  • A function component
  • A class component
props
object | null
An object containing the element’s properties/attributes. Special props:
  • key: Unique identifier for efficient reconciliation
  • ref: Reference to the DOM node or component instance
  • All other props are passed to the component or set as attributes
children
ComponentChildren
Child elements. Can be:
  • VNodes created with createElement()
  • Strings or numbers (rendered as text)
  • Arrays of children
  • null, undefined, or booleans (not rendered)

Return Value

Returns a VNode<P> object representing the virtual DOM node.

Description

createElement is Preact’s React-compatible function for creating virtual DOM nodes. It’s identical to the h function but uses the React naming convention. This function is the target of JSX transformations when using the React JSX pragma. It can also be called directly for creating virtual nodes programmatically.

Implementation

The function is implemented in src/create-element.js:16:
export function createElement(type, props, children) {
  let normalizedProps = {},
    key,
    ref,
    i;
  for (i in props) {
    if (i == 'key') key = props[i];
    else if (i == 'ref' && typeof type != 'function') ref = props[i];
    else normalizedProps[i] = props[i];
  }

  if (arguments.length > 2) {
    normalizedProps.children =
      arguments.length > 3 ? slice.call(arguments, 2) : children;
  }

  return createVNode(type, normalizedProps, key, ref, NULL);
}

Usage Examples

JSX with React Pragma

import { createElement } from 'preact';

// JSX automatically transforms to createElement calls
function App() {
  return (
    <div className="app">
      <h1>Hello World</h1>
      <p>Welcome to Preact</p>
    </div>
  );
}

// Equivalent to:
// createElement('div', { className: 'app' },
//   createElement('h1', null, 'Hello World'),
//   createElement('p', null, 'Welcome to Preact')
// )

Direct Function Calls

import { createElement } from 'preact';

// Create a simple element
const heading = createElement('h1', { className: 'title' }, 'Hello World');

// Create nested elements
const section = createElement('section', null,
  createElement('h2', null, 'Section Title'),
  createElement('p', null, 'Section content')
);

Creating Components

import { createElement } from 'preact';

function Button({ label, onClick }) {
  return createElement('button', { onClick }, label);
}

function App() {
  return createElement(Button, {
    label: 'Click me',
    onClick: () => alert('Clicked!')
  });
}

With Props and Event Handlers

import { createElement } from 'preact';

const input = createElement('input', {
  type: 'text',
  placeholder: 'Enter your name',
  value: 'John',
  onChange: (e) => console.log(e.target.value),
  onFocus: () => console.log('Focused'),
  className: 'text-input'
});

Lists with Keys

import { createElement } from 'preact';

function TodoList({ todos }) {
  return createElement('ul', null,
    todos.map(todo =>
      createElement('li', { key: todo.id }, todo.text)
    )
  );
}

Using Refs

import { createElement, createRef } from 'preact';
import { useEffect } from 'preact/hooks';

function AutoFocusInput() {
  const inputRef = createRef();
  
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  
  return createElement('input', {
    ref: inputRef,
    type: 'text',
    placeholder: 'This will be focused'
  });
}

Conditional Rendering

import { createElement } from 'preact';

function Greeting({ isLoggedIn, username }) {
  return createElement('div', null,
    isLoggedIn
      ? createElement('p', null, `Welcome back, ${username}!`)
      : createElement('p', null, 'Please log in')
  );
}

Spreading Props

import { createElement } from 'preact';

function CustomInput(props) {
  // Spread props onto native input
  return createElement('input', {
    ...props,
    className: `custom-input ${props.className || ''}`
  });
}

const input = createElement(CustomInput, {
  type: 'email',
  placeholder: 'Email address',
  required: true
});

Special Behaviors

Key Prop

The key prop is extracted and not passed to the component:
// key is used internally for reconciliation
const item = createElement('div', { key: '123', id: 'item-123' });
// The component receives: { id: 'item-123' }
// The key is stored separately on the VNode

Ref Prop

The ref prop is extracted for DOM elements but passed through for function components:
// For DOM elements - ref is extracted
const div = createElement('div', { ref: myRef });

// For function components - ref is passed as prop
function MyComponent({ ref }) {
  // ref is available as a prop
}
const component = createElement(MyComponent, { ref: myRef });

Children Normalization

Children are automatically collected from all arguments after props:
// Single child
createElement('div', null, 'text');
// props.children = 'text'

// Multiple children
createElement('div', null, 'text', createElement('span'));
// props.children = ['text', VNode]

// Children as prop (takes precedence)
createElement('div', { children: 'from props' }, 'from args');
// props.children = 'from args' (arguments override props)

JSX Configuration

Configure JSX to use createElement with React-compatible settings: TypeScript (tsconfig.json)
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "createElement",
    "jsxFragmentFactory": "Fragment"
  }
}
Babel (.babelrc)
{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "createElement",
      "pragmaFrag": "Fragment"
    }]
  ]
}
Or use automatic runtime (React 17+)
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

Performance Notes

  • The function normalizes props efficiently by only copying non-special props
  • Variadic children arguments are only allocated when multiple children exist
  • VNode creation is optimized with object shape consistency
  • h - Identical function with hyperscript naming
  • Fragment - Render multiple children without a wrapper
  • cloneElement - Clone and modify existing VNodes
  • render - Render VNodes to the DOM

Build docs developers (and LLMs) love