@expo/html-elements
Simple, light-weight, and well tested, universal semantic HTML elements as React components for iOS, Android, web, and desktop apps!
We at Expo recommend using platform agnostic primitives like View
, Image
, and Text
whenever possible but sometimes that's not easy. Some primitives like Tables, and Footers are native to web only and currently have no way of easily accessing. This package aims to solve that while still being an optimal UI package for iOS, and Android.
What you get
- Using
@expo/html-elements
will optimize your website for SEO and accessibility. Meaning your websites are indexed more accurately and your native apps better accommodate physically impaired users.
- This package takes full advantage of
react-native-web
a11y rules whenever possible. - For example, the
H1
component will render an <h1 />
on web, a UILabel
on iOS, and a TextView
on Android.
- Every component can accept styles from the
StyleSheet
API. - TypeScript works for iOS, Android, and web, no more having to create monkey patches to use
href
on a Text
element. - This package is completely side-effect free!
Setup
Install:
yarn add @expo/html-elements
Import and use the package:
import { H1 } from '@expo/html-elements';
Components
Here is a list of all the currently supported elements and the web feature they map to. Not all HTML elements are supported. There are some HTML elements that mostly overlap with some universal modules, you should always try to use the universal modules whenever possible. All supported components are a capitalized variation of the semantic HTML they implement/emulate.
External
Other features not implemented in this package can be found in different parts of the Expo ecosystem.
HTML | Universal | Package |
---|
<audio /> | Audio | expo-av |
<button /> | <Button /> | react-native |
<input type="text" /> | <TextInput /> | react-native |
<input type="file" /> | ImagePicker | expo-image-picker |
<input type="file" /> | DocumentPicker | expo-document-picker |
<canvas /> | <GLView /> | expo-gl & Expo Canvas |
<iframe /> | <WebView /> | <WebView /> . @react-native-community/web-view is not maintained by Expo and doesn't have web support |
<link /> | None | Eject the index.html with npx expo customize and link resources directly with <link /> |
<noscript /> | None | Eject the index.html with npx expo customize and use <noscript /> directly as HTML |
<div /> | <View /> | react-native |
<img /> | <Image /> | react-native |
<span /> | <Text /> | react-native |
<video /> | <Video /> | expo-av |
style="backdrop-filter" | <BlurView /> | expo-blur |
style="linear-gradient()" | <LinearGradient /> | expo-linear-gradient |
Headings
Header elements will use the expected font size and margins from web universally. You can see how the native CSS units (rem, and em) are transformed in css/units.
import { H1, H2, H3, H4, H5, H6 } from '@expo/html-elements';
<H1/>
import { H1 } from '@expo/html-elements';
export default () => <H1>Example<H1/>
Platform | Output |
---|
Web | <h1 aria-level="1" dir="auto" role="heading" /> |
Native | <Text role="header" style={[Custom]} /> |
<H2/>
import { H2 } from '@expo/html-elements';
export default () => <H2>Example<H2/>
Platform | Output |
---|
Web | <h2 aria-level="2" dir="auto" role="heading" /> |
Native | <Text role="header" style={[Custom]} /> |
<H3/>
import { H3 } from '@expo/html-elements';
export default () => <H3>Example<H3/>
Platform | Output |
---|
Web | <h3 aria-level="3" dir="auto" role="heading" /> |
Native | <Text role="header" style={[Custom]} /> |
<H4/>
import { H4 } from '@expo/html-elements';
export default () => <H4>Example<H4/>
Platform | Output |
---|
Web | <h4 aria-level="4" dir="auto" role="heading" /> |
Native | <Text role="header" style={[Custom]} /> |
<H5/>
import { H5 } from '@expo/html-elements';
export default () => <H5>Example<H5/>
Platform | Output |
---|
Web | <h5 aria-level="5" dir="auto" role="heading" /> |
Native | <Text role="header" style={[Custom]} /> |
<H6/>
import { H6 } from '@expo/html-elements';
export default () => <H6>Example<H6/>
Platform | Output |
---|
Web | <h6 aria-level="6" dir="auto" role="heading" /> |
Native | <Text role="header" style={[Custom]} /> |
Link
<A/>
You can use the anchor element with href prop to open links. On native this will attempt to use the Linking
API to open the href
.
- The CSS style is fully normalized to match
<Text />
- For pseudo-class effects like hover and focus states check out the package
react-native-web-hooks
| tutorial
import { A } from '@expo/html-elements';
export default () => <A href="#" target="_blank" />;
}
Platform | Output |
---|
Web | <a data-focusable="{true}" dir="auto" href="#" role="link" target="_blank" /> |
Native | <Text role="link" onPress={[Function]} /> |
Layout
You can use layout elements like Header, Main, Footer, Section, Nav, etc. as a drop-in replacement for View
s in your existing app.
Default Layout style
All layout HTML elements inherit the shared style of <View />
to accommodate the Yoga layout engine which we use on native for iOS, and Android.
display
is always flex
. This is because Yoga only implements display: flex
.flex-direction
is always column
instead of row
.
Why use Layout elements
Consider the following: in your app you have a basic element at the top which wraps the buttons and title. A screen reader doesn't understand that this is a header, and mostly neither does a web crawler. But if you replace the encasing view with a <Header />
the following happens:
Some elements like Footer
and Main
have no iOS, or Android enhancements, but they'll still improve web. Using the proper HTML 5 elements will make your layout compliant with the HTML5 outline algorithm.
<Div/>
Renders a <div />
on web and a View
with no ARIA set on mobile.
import { Div } from '@expo/html-elements';
export default () => (
<Div>
<P>Some content in the main element</P>
</Div>
);
)
Platform | Output |
---|
Web | <div /> |
Native | <View /> |
<Nav/>
import { Nav } from '@expo/html-elements';
export default () => <Nav />;
Platform | Output |
---|
Web | <nav style="display:flex" /> |
Native | <View /> |
Renders a <header />
on web with ARIA set to banner
and a View
with ARIA set to header
on mobile.
import { Header } from '@expo/html-elements';
export default () => <Header />;
<Main/>
Renders a <main />
on web with ARIA role
set to main
and a View
with no ARIA set on mobile.
import { Main } from '@expo/html-elements';
export default () => (
<Main>
<P>Some content in the main element</P>
</Main>
);
)
Platform | Output |
---|
Web | <main role="main" style="display:flex" /> |
Native | <View /> |
<Section/>
Renders a <section />
on web with ARIA set to region
and a View
with ARIA set to summary
on mobile.
import { Section } from '@expo/html-elements';
export default () => <Section />;
Platform | Output |
---|
Web | <section role="region" style="display:flex" /> |
Native | <View role="summary" /> |
<Article/>
Renders an <article />
on web and a View
everywhere else.
import { Article } from '@expo/html-elements';
export default () => <Article />;
Platform | Output |
---|
Web | <article role="article" /> |
Native | <View /> |
<Aside/>
import { Aside } from '@expo/html-elements';
export default () => <Aside />;
Platform | Output |
---|
Web | <aside role="complementary" /> |
Native | <View /> |
Renders an <footer />
on web and a View
everywhere else.
import { Footer } from '@expo/html-elements';
export default () => <Footer />;
Platform | Output |
---|
Web | <footer role="contentinfo" /> |
Native | <View /> |
Text
Text elements currently use Text
universally rendering either a div
or span
to emulate Yoga style properly.
- Style is modified to match web.
- All font styles are reset (minus
Code
, and Pre
). - All elements accept styles from
StyleSheet
API.
import { P, B, S, I, BR, Code } from '@expo/html-elements';
export default () => (
<>
<P>
Hello<B>World (in bold)</B>
</P>
<S>strike text</S>
<BR />
<I>Italic</I>
<Code>const foo = true</Code>
</>
);
<P/>
Standard paragraph element.
Platform | Output |
---|
Universal | <Text style={{ fontSize: 14, marginVertical: '1em' }} /> |
<B/>
Bold text text.
Platform | Output |
---|
Universal | <Text style={{ fontWeight: 'bold' }} /> |
<Strong/>
Alternate bold text.
Platform | Output |
---|
Universal | <Text style={{ fontWeight: 'bold' }} /> |
<Span/>
Inline text element.
Platform | Output |
---|
Universal | <Text /> |
<S/>
Strike through text.
Platform | Output |
---|
Universal | <Text style={{ textDecorationLine: 'line-through' }} /> |
<Del/>
Alternate strike through text.
Platform | Output |
---|
Universal | <Text style={{ textDecorationLine: 'line-through' }} /> |
<I/>
Italic text.
Platform | Output |
---|
Universal | <Text style={{ fontStyle: 'italic' }} /> |
<EM/>
Alternate italic text.
Platform | Output |
---|
Universal | <Text style={{ fontStyle: 'italic' }} /> |
<Code/>
Inline code block with fontFamily: 'Courier'
on iOS and Web, fontFamily: 'monospace'
on Android.
Platform | Output |
---|
Universal | <Text style={[Custom]} /> |
<Pre/>
Render a preformatted code block with fontFamily: 'Courier'
on iOS and Web, fontFamily: 'monospace'
on Android.
<Pre>{`
body {
color: red;
}
`}</Pre>
<Pre>
<Code>{`const val = true`}</Code>
</Pre>
Platform | Output |
---|
Universal | <Text style={[Custom]} /> | <View /> |
<Mark/>
Highlight text.
Platform | Output |
---|
Universal | <Text style={{ backgroundColor: 'yellow', color: 'black' }} /> |
<Q/>
Quoted text.
Platform | Output |
---|
Universal | <Text style={[Custom]}>"{props.children}"</Text> |
<BlockQuote/>
Platform | Output |
---|
Universal | <View style={[Custom]} /> |
<Time/>
dateTime
prop is supported on web and stripped on native.
Platform | Output |
---|
Universal | <Text style={[Custom]} /> |
Lists
Lists can be used to create basic bulleted or numbered lists. You should try and use universal FlatList
or SectionList
components for long scrolling lists instead of these.
<UL/>
Create an unordered (bulleted) list <ul />
on web, and emulates the style with a <View />
on native.
import { UL, LI } from '@expo/html-elements';
export default () => (
<UL>
<LI>oranges</LI>
<LI>apples</LI>
<UL>
<LI>green</LI>
<LI>red</LI>
</UL>
</UL>
);
Platform | Output |
---|
Web | <ul /> |
Native | <View style={[Custom]} /> |
<LI/>
Create a standard list item <li />
on web and a native view on mobile which can render text or views inside it.
Platform | Output |
---|
Web | <li /> |
Native | <Text style={[Custom]} /> | <View style={[Custom]} /> |
Rules
<HR/>
Renders a <View>
everywhere. Style is modified to match web.
import { HR } from '@expo/html-elements';
export default () => <HR />;
Platform | Output |
---|
Web | <hr /> |
Native | <View style={[Custom]} /> |
<BR/>
Create a line break.
Platform | Output |
---|
Web | <br /> |
Native | <View style={{height: 8, width: 0}} /> |
Tables
Create tables universally.
- Each element renders to the expected type on web.
padding
is removed from all table elements.- Text can only be rendered in
TH
and TD
on mobile. colSpan
and rowSpan
are currently web-only (PRs welcome).
import { Table, THead, TH, TBody, TFoot, TR, TD, Caption } from '@expo/html-elements';
import { Text } from 'react-native';
export default () => (
<Table>
<Caption>Caption</Caption>
<THead>
<TR>
<TH colSpan="2">The table header</TH>
</TR>
</THead>
<TBody>
<TR>
<TD>The table body</TD>
<TD>with two columns</TD>
</TR>
</TBody>
<TFoot>
<TR>
<TD>
<Text>This is the table footer</Text>
</TD>
</TR>
</TFoot>
</Table>
);
Table example output web
<table>
<caption>
Caption
</caption>
<thead>
<tr>
<th colspan="2">The table header</th>
</tr>
</thead>
<tbody>
<tr>
<td>The table body</td>
<td>with two columns</td>
</tr>
</tbody>
<tfoot>
<tr>
<td><div>The table body</div></td>
</tr>
</tfoot>
</table>
<Table/>
Base element for creating a Table.
Platform | Output |
---|
Web | <table /> |
Native | <View style={[Custom]} /> |
<THead/>
Header element in a Table.
Platform | Output |
---|
Web | <thead /> |
Native | <View style={[Custom]} /> |
<TBody/>
Body element in a Table.
Platform | Output |
---|
Web | <tbody /> |
Native | <View style={[Custom]} /> |
Footer element in a Table.
Platform | Output |
---|
Web | <tfoot /> |
Native | <View style={[Custom]} /> |
<TH/>
Used to display text in the Header.
colSpan
and rowSpan
are currently web-only.
Platform | Output |
---|
Web | <th /> |
Native | <Text style={[Custom]} /> |
<TR/>
Used to create a Row in a Table.
Platform | Output |
---|
Web | <tr /> |
Native | <View style={[Custom]} /> |
<TD/>
Create a cell in a Table.
colSpan
and rowSpan
are currently web-only.
Platform | Output |
---|
Web | <td /> |
Native | <View style={[Custom]} /> |
<Caption/>
Used to caption your table. Excepts text as a child.
Platform | Output |
---|
Web | <caption /> |
Native | <Text style={[Custom]} /> |
TODO
- Improve relative imports for better tree-shaking.
Babel
You can write react-dom
elements in your code and use the babel plugin to transform them to @expo/html-elements
elements.
module.exports = {
plugins: ['@expo/html-elements/babel'],
};
Input
export default function Page() {
return (
<div>
<h1>Hello World</h1>
</div>
);
}
Output
The import is automatically added if it's not already present. All props are passed through without any additional transforms.
import { Div, H1 } from '@expo/html-elements';
export default function Page() {
return (
<Div>
<H1>Hello World</H1>
</Div>
);
}
Contributing
Contributions are very welcome! Please refer to guidelines described in the contributing guide.