skip to content

In JSX, why must a component tag be written `<Profile />` rather than `<profile />`, and what does React actually do if you lowercase it?

level: juniorimportance: must knowfreq 68%

answer

  1. the compiler reads the first character
  2. one becomes a string, one a binding
  3. host tag versus component reference
  4. no error, just an unknown DOM element
  5. dot notation escapes the rule

basics

~20 s

Capitalization tells the JSX compiler whether the tag is a value or a string. A capitalized tag compiles to a reference to the variable of that name; a lowercase tag compiles to the string "profile", so React renders a literal DOM tag and your component never runs.

solid answer

~50 s

The JSX transform decides by the first character. `<Profile />` compiles to `jsx(Profile, {})` — a reference to the in-scope binding named `Profile`, so the compiler will error if that binding does not exist. `<profile />` compiles to `jsx("profile", {})`: the tag name becomes a plain string, which React treats as a host (DOM) tag. Nothing throws; React just puts a literal `<profile>` element into the DOM, your props end up as attributes, and the component you meant to render is never called — usually surfacing as a mysteriously blank area. The rule is purely lexical: React has no registry of component names to look up. The one exception is a member expression: `<ui.Button />` or `<icons.chevron />` is always compiled as a value, because a dotted path can never be a valid HTML tag name.

code

jsx · 17 lines
jsx
function Profile() {
  return <p>Ada</p>;
}

const icons = { chevron: () => <span>v</span> };

export function Demo() {
  const Dynamic = icons.chevron; // capitalized local: renders as a component
  return (
    <>
      <Profile />       {/* _jsx(Profile, {}) — component runs */}
      <profile />       {/* _jsx("profile", {}) — literal DOM tag, blank */}
      <icons.chevron /> {/* member expression: always a value */}
      <Dynamic />
    </>
  );
}

go deeper

for a junior

Know the rule cold and be able to state the consequence: lowercase means React renders a DOM tag with that literal name, so your component quietly never runs.

for a middle

Explain it through the transform — capitalized compiles to an identifier reference, lowercase to a string — and mention the member-expression exception and the capitalized-local idiom for dynamic component types.

for a senior

Be able to debug the symptom from the other end: a blank region with an unknown tag in the DOM inspector, and connect it to how a registry or dynamic mapping was wired.

for a principal

Frame it as an API design point — namespaced compound components rely on member expressions, and a component registry needs a documented resolution convention so consumers do not hit the silent-lowercase trap.

## The rule In JSX, a tag whose name starts with a **lowercase letter** is compiled to a **string**; a tag starting with an **uppercase letter** is compiled to a **reference to a variable of that name**. That is the entire rule, and it lives in the compiler, not in React. ```js // source <Profile size={3} /> <section id="a" /> // output (automatic runtime) _jsx(Profile, { size: 3 }); // the binding Profile _jsx("section", { id: "a" }); // the string "section" ``` The transform needs a way to distinguish "render the built-in `<section>` element" from "render the component the identifier `Section` points to", and it has only the source text to go on. Since HTML tag names are lowercase by convention, capitalization is the cheapest available signal. ## What React does with each When `type` is a **function** (or a class), React calls it with the props to get more elements back. When `type` is a **string**, React treats it as a host component and asks the renderer — `react-dom` in the browser — to create that node and set the props on it as attributes and event handlers. So a lowercase component tag is not a syntax error and does not warn about a missing component. React faithfully does what you asked: it creates a DOM element literally named `profile`. The browser has no such element, so it is rendered as an unknown inline element with no styling and no children of its own; your `<Profile>` implementation is never invoked. In a large tree this shows up as a silently empty region, which is why the mistake is worth being able to explain rather than just avoid. ## Consequences you can name Because a capitalized tag is a *variable reference*, several things follow that surprise people: - **Scope matters, not file names.** `<Profile />` fails to compile if nothing named `Profile` is imported or declared in that module — the error is an ordinary "Profile is not defined", the same one you would get from a normal identifier. - **You can rename on import.** `import { Chart as Graph } from "./chart"` then `<Graph />` works, because the tag resolves the local binding. - **A component held in a lowercase variable will not render as a tag.** `const icon = iconMap[name]; <icon />` compiles to `_jsx("icon", {})`. Assign it to a capitalized local first — `const Icon = iconMap[name]; <Icon />` — which is the standard "dynamic component type" idiom. - **Dot notation always resolves a value.** `<ui.Button />` and even `<icons.chevron />` compile to `_jsx(ui.Button, {})` / `_jsx(icons.chevron, {})`, because a member expression cannot be an HTML tag name. This is what makes compound-component namespaces expressible. ```js const icons = { chevron: () => <svg />, close: () => <svg /> }; // works: member expression, case irrelevant <icons.chevron /> // does NOT work: compiles to the string "chevron" const chevron = icons.chevron; <chevron /> ``` ## Custom elements Lowercase tags are also how you render real custom elements: `<my-widget />` compiles to `_jsx("my-widget", {})`, which is correct — you want the string, because the browser's custom-element registry, not React, owns that tag. React 19 improved this path: primitive props are set as attributes while non-primitive props such as objects and functions are assigned as properties on the element, which is what most custom elements expect. ## Naming details that follow from the same rule Attribute names in JSX are subject to JavaScript's rules, not HTML's, which is the same underlying reason: they become object keys in a compiled object literal. Hence `className` rather than `class` and `htmlFor` rather than `for` — both of the HTML names are reserved words in JavaScript. Hyphenated names still work as quoted-style attributes for host tags (`data-*` and `aria-*` are passed through as written), because they map to attributes rather than to identifiers. ## How to answer in an interview Say the rule, show the two compiled lines, and then name the failure mode: a lowercase component tag silently renders an unknown DOM element instead of the component. Adding the dot-notation exception and the "assign to a capitalized local for a dynamic component" idiom takes the answer from correct to complete.

  • You have a component stored in a lowercase variable chosen at runtime. How do you render it?
    Assign it to a capitalized local binding first — `const Component = registry[kind];` then `<Component {...props} />` — or reach it through a member expression such as `<registry.chart />`. Both compile to a value reference; a bare lowercase identifier tag would compile to the string tag name instead.
  • Does the capitalization rule apply to web components like `<my-widget />`?
    No, and you want it that way: a hyphenated lowercase tag compiles to the string `"my-widget"`, which is exactly what a browser custom element needs, since the element is registered with the browser rather than defined in React. React 19 sets primitive props as attributes and object or function props as properties on such elements.
  • What error do you get from `<Profile />` when nothing named `Profile` is imported?
    An ordinary JavaScript reference error — `Profile is not defined` — at build time or on first evaluation, because the tag compiled to a plain identifier reference. There is no React-specific "unknown component" message, which is a good hint that the tag is nothing more than a variable.

saying these in an interview costs you the question

  • Says capitalization is just a naming convention
  • Claims React looks components up by name in a registry
  • Believes a lowercase component tag throws an error
  • Thinks `<profile />` still renders the Profile component
  • Cannot explain why `<ui.Button />` works

context