434 | );
435 | }
436 |
--------------------------------------------------------------------------------
/example/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/example/next.config.js:
--------------------------------------------------------------------------------
1 | const withNextra = require('nextra')({
2 | theme: 'nextra-theme-docs',
3 | themeConfig: './theme.config.tsx',
4 | });
5 |
6 | module.exports = withNextra();
7 |
--------------------------------------------------------------------------------
/example/package-prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front-end",
3 | "version": "0.1.0",
4 | "private": false,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "next": "^12.2.4",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "react-syntax-highlighter": "^15.4.3",
15 | "react-type-animation": "^2.1.2"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^18.6.4",
19 | "@types/react": "^18.0.16",
20 | "@types/react-dom": "^18.0.6",
21 | "@types/tailwindcss": "^2.0.2",
22 | "@typescript-eslint/eslint-plugin": "^4.0.0",
23 | "@typescript-eslint/parser": "^4.0.0",
24 | "autoprefixer": "^10.2.5",
25 | "babel-eslint": "^10.0.0",
26 | "eslint": "^7.5.0",
27 | "eslint-config-react-app": "^6.0.0",
28 | "eslint-plugin-flowtype": "^5.2.0",
29 | "eslint-plugin-import": "^2.22.0",
30 | "eslint-plugin-jsx-a11y": "^6.3.1",
31 | "eslint-plugin-react": "^7.20.3",
32 | "eslint-plugin-react-hooks": "^4.0.8",
33 | "postcss": "^8.2.9",
34 | "prettier": "^2.2.1",
35 | "tailwindcss": "^2.1.1",
36 | "typescript": "^4.7.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front-end",
3 | "version": "0.1.0",
4 | "private": false,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "grapheme-splitter": "^1.0.4",
12 | "next": "^12.3.4",
13 | "nextra": "^2.2.19",
14 | "nextra-theme-docs": "^2.2.19",
15 | "react-syntax-highlighter": "^15.4.3",
16 | "react-type-animation": "latest"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^18.6.4",
20 | "@types/react": "^18.0.16",
21 | "@types/react-dom": "^18.0.6",
22 | "@types/tailwindcss": "^2.0.2",
23 | "@typescript-eslint/eslint-plugin": "^4.0.0",
24 | "@typescript-eslint/parser": "^4.0.0",
25 | "autoprefixer": "^10.2.5",
26 | "babel-eslint": "^10.0.0",
27 | "eslint": "^7.5.0",
28 | "eslint-config-react-app": "^6.0.0",
29 | "eslint-plugin-flowtype": "^5.2.0",
30 | "eslint-plugin-import": "^2.22.0",
31 | "eslint-plugin-jsx-a11y": "^6.3.1",
32 | "eslint-plugin-react": "^7.20.3",
33 | "eslint-plugin-react-hooks": "^4.0.8",
34 | "postcss": "^8.2.9",
35 | "prettier": "^2.2.1",
36 | "react": "^18.2.0",
37 | "react-dom": "^18.2.0",
38 | "tailwindcss": "^3.2.7",
39 | "typescript": "^4.7.4"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/example/pages/_app.mdx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 |
3 | export default function App({ Component, pageProps }) {
4 | return
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/example/pages/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Get Started",
3 | "examples": "Examples",
4 | "options": "Options / Props",
5 | "wrapper-css": "Wrapper & CSS tips",
6 | "accessibility": "Accessibility"
7 | }
8 |
--------------------------------------------------------------------------------
/example/pages/accessibility.mdx:
--------------------------------------------------------------------------------
1 | import { Callout } from 'nextra-theme-docs';
2 |
3 | # Accessibility
4 |
5 | Because the typing animation _A)_ delays and _B)_ constantly manipulates the written text, it is not only bothersome but sometimes even **impossible for screen readers** to capture the entire text at once.
6 |
7 | If your type animation component includes **actual text ("content")** or conveys an important message that is not purely decorative, you should make your typewriter animation accessible to screen readers.
8 |
9 | ## Visually-hidden Class
10 |
11 | The perhaps best approach to make a typewriter animation accessible is **1.)** additionally rendering the conveyed message of the `` in a separate wrapper with a _visually-hidden_ class that only hides the content for sighted users and **2.)** setting `aria-hidden="true"` on the `` to remove it from the a11y tree:
12 |
13 | ```tsx {2-16,18}
14 |
15 |
27 | {/* The most important content of the typewriter animation: Hidden from sighted viewers but (in most cases) accessible to screen readers */}
28 | We produce food for Mice, Hamsters, Guinea Pigs and Chinchillas
29 |
30 |
44 |
45 | ```
46 |
47 | ## Alternative: aria-label
48 |
49 | One can also add an `aria-label` directly on the `` component to convey the most important contents of the typewriter animation:
50 |
51 | ```tsx {2}
52 |
69 | ```
70 |
71 | Setting an `aria-label` requires setting a `role` as well. The role [`marquee`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/marquee_role) is probably the most suitable for this situation.
72 |
73 | By applying an `aria-label`, the dynamically typed contents of your sequence will automatically be wrapped in a `` with `aria-hidden="true"` and removed from the accessibility tree:
74 |
75 | ```tsx {aria-hidden="true"}
76 | {/* Rendered HTML: */}
77 |
80 | We pro [...]
81 |
82 | ```
83 |
84 |
85 | Note: `aria-label` should generally only be used on interactive elements,
86 | which may make this approach not ideal. Certain screen readers may even
87 | disregard this aria-label entirely as it is applied to a non-interactive
88 | wrapper element.
89 |
90 |
--------------------------------------------------------------------------------
/example/pages/examples.mdx:
--------------------------------------------------------------------------------
1 | import {
2 | CallbackExample,
3 | ContinuationExample,
4 | StateManipulationColorExample,
5 | LandingPageExample,
6 | LandingPagePreTypedExample,
7 | ReplacementExample,
8 | CustomSpeedExample,
9 | MultipleLinesExample,
10 | RemoveCursorExample,
11 | SplitterByWordExample,
12 | SplitterComplexCharactersExample
13 | } from '../components/examples';
14 | import { Callout, Tabs, Tab } from 'nextra-theme-docs';
15 |
16 | # Examples
17 |
18 | ---
19 |
20 | ## Landing Page Animations
21 |
22 | ### Dynamic
23 |
24 | Include the initial text in every string, and the **static part will only be typed out once**.
25 |
26 |
27 |
28 |
29 |
30 | ```tsx copy
31 |
47 | ```
48 |
49 |
50 | Note: Typing complex characters like emojis requires a custom [splitter function](#typing-complex-characters).
51 |
52 |
53 |
54 |
55 | ### Initially Pre-rendered
56 |
57 | By using the `preRenderFirstString` prop, you can initially (pre-)render the very first string of your sequence. When used with SSR (Next.js or similar), the initial string will be included in the static HTML, which may benefit SEO.
58 |
59 |
60 |
61 |
62 |
63 | ```tsx copy {3}
64 |
65 |
82 |
83 | ```
84 |
85 | ---
86 |
87 | ## Continuation
88 |
89 |
90 |
91 |
92 |
93 | ```tsx copy
94 |
112 | ```
113 |
114 | ---
115 |
116 | ## Replacement
117 |
118 |
119 |
120 |
121 |
122 | ```tsx copy
123 |
128 | ```
129 |
130 | ---
131 |
132 | ## Custom Speed
133 |
134 | As mentioned in the [props](/options#component-props) section, you can specify both the typing `speed` and `deletionSpeed` with a simple relative number between 1-99 or an exactly specified keystroke delay.
135 |
136 |
137 |
138 |
139 |
140 |
141 | Note: The animation **adds a random delay relative to your
142 | provided `speed` and `deletionSpeed`** after each keystroke to make the typing
143 | animation look more natural.
144 |
145 |
146 | ---
147 |
148 | ## Multiple Lines
149 |
150 | By addding the `white-space: pre-line` css style and placing `'\n'` anywhere in your text, or making actual line breaks inside a string literal, you can write in multiple lines.
151 |
152 |
153 |
154 |
155 |
156 | ```tsx copy /whiteSpace: 'pre-line'/ /\n/
157 |
168 | ```
169 |
170 |
171 | Using the explicit `\n` new-line is preferred, because your code formatter may
172 | add spaces in new lines of the string literal that will be typed out as an
173 | empty string and hence unintentionally delay the animation.
174 |
175 |
176 |
177 | Pre-define the height and width of the parent element to prevent layout shift
178 | when typing multi-lines
179 |
180 |
181 | ---
182 |
183 | ## Callback Functions
184 |
185 | Use callback functions at any place inside of your animation
186 | sequence to perform any (global) actions you want. An exemplary
187 | use-case for this is calling functions or state updates that [manipulate the styles](#manipulation-via-state) of your animation component, or let your
188 | application know at which state of typing the animation currently
189 | is, and adjusting some other visual elements accordingly.
190 |
191 |
192 |
193 |
194 |
195 | ```jsx copy
196 | const [typingStatus, setTypingStatus] = useState('Initializing');
197 |
198 | {
202 | setTypingStatus('Typing...');
203 | },
204 | 'Use callback-functions to trigger events',
205 | () => {
206 | setTypingStatus('Done Typing');
207 | },
208 | 1000,
209 | () => {
210 | setTypingStatus('Deleting...');
211 | },
212 | '',
213 | () => {
214 | setTypingStatus('Done Deleting');
215 | },
216 | ]}
217 | repeat={Infinity}
218 | />;
219 | ```
220 |
221 | ### Manipulation via CSS Classes
222 |
223 | It's possible to manipulate the animation styles in order to, for example, **stop the cursor animation at a specific point within the animation sequence**:
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 | ```tsx {6,10,17,19}
234 | const CURSOR_CLASS_NAME = 'custom-type-animation-cursor';
235 |
236 | return (
237 | <>
238 | el.classList.remove(CURSOR_CLASS_NAME), // A reference to the element gets passed as the first argument of a callback function
251 | 6000,
252 | (el) => el.classList.add(CURSOR_CLASS_NAME),
253 | '',
254 | ]}
255 | repeat={Infinity}
256 | />
257 |
258 |
269 | >
270 | );
271 | ```
272 |
273 |
274 |
275 |
276 | ```tsx {1,10,12,14,22-23,27,34,36}
277 | const ref = React.createRef(); // HTMLSpanElement because 'span' is the default wrapper element of the component
278 |
279 | const CURSOR_CLASS_NAME = 'custom-type-animation-cursor';
280 |
281 | const showCursorAnimation = (show: boolean) => {
282 | if (!ref.current) {
283 | return;
284 | }
285 |
286 | const el = ref.current;
287 | if (show) {
288 | el.classList.add(CURSOR_CLASS_NAME);
289 | } else {
290 | el.classList.remove(CURSOR_CLASS_NAME);
291 | }
292 | };
293 |
294 | return (
295 |
296 | <>
297 | showCursorAnimation(false),
311 | 2000,
312 | () => showCursorAnimation(true),
313 | '',
314 | ]}
315 | repeat={Infinity}
316 | />
317 |
318 | {/* Copy over the default typing styles. Also works with simple global css files or css modules */}
319 |
330 |
331 | >
332 | );
333 | ```
334 |
335 |
336 |
337 |
338 | ### Manipulation via State
339 |
340 | By applying dynamic styles to **the parent element** of the `TypeAnimation` component, you can easily manipulate styles without classNames and passing ref.
341 |
342 |
343 |
344 |
345 |
346 | ```tsx {1,8,17,20,23,43}
347 | const [textColor, setTextColor] = useState('red');
348 |
349 | return (
350 | <>
351 |
375 |
394 | >
395 | );
396 | ```
397 |
398 |
399 |
400 | ## Custom String Splitter
401 |
402 | By default, strings placed inside the `sequence` are split character by character, to simulate keyboard-like typing. With he help of the `splitter` prop, it's possible to define a custom splitting of sequence strings.
403 |
404 | ### Typing Word by Word
405 |
406 | To create a word-level typing animation similar to ChatGPT or other AI chatbots, we can **split strings into single words**, rather than characters.
407 |
408 |
409 | ```tsx {2,8}
410 | str.split(/(?= )/)} // 'Lorem ipsum dolor' -> ['Lorem', ' ipsum', ' dolor']
412 | sequence={[
413 | 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
414 | 3000,
415 | '',
416 | ]}
417 | speed={{ type: 'keyStrokeDelayInMs', value: 30 }}
418 | omitDeletionAnimation={true}
419 | style={{ fontSize: '1em', display: 'block', minHeight: '200px' }}
420 | repeat={Infinity}
421 | />
422 | ```
423 |
424 | ### Typing Complex Characters
425 |
426 | As certain complex Unicode characters, like **emojis**, are internally represented as multiple characters in JavaScript, including them in our animation requires an advanced string splitter, such as [grapheme-splitter](https://www.npmjs.com/package/grapheme-splitter), capable of splitting those characters into *extended grapheme clusters* (single letters).
427 |
428 |
429 |
430 |
431 | ```tsx {1,3,7}
432 | import GraphemeSplitter from 'grapheme-splitter'; // npm i grapheme-splitter
433 |
434 | const splitter = new GraphemeSplitter();
435 |
436 | return (
437 | splitter.splitGraphemes(str)}
439 | sequence={[
440 | 'Hello 🇬🇧',
441 | 2000,
442 | 'Ciao 🇮🇹',
443 | 2000,
444 | '你好 🇨🇳',
445 | 2000,
446 | 'Здравейте 🇧🇬 ',
447 | 2000,
448 | 'Hola 🇪🇸',
449 | 2000,
450 | 'Bonjour 🇫🇷',
451 | 2000,
452 | 'नमस्ते 🇮🇳',
453 | 2000
454 | ]}
455 | style={{ fontSize: '2em' }}
456 | repeat={Infinity}
457 | />
458 | );
459 | ```
--------------------------------------------------------------------------------
/example/pages/index.mdx:
--------------------------------------------------------------------------------
1 | import TypeAnimation from '../components/TypeAnimation';
2 | import { Callout, Tab, Tabs, Steps } from 'nextra-theme-docs';
3 |
4 | # Get Started
5 |
6 |
7 |
8 | ### Installation
9 |
10 |
11 |
12 |
13 | ```bash copy
14 | npm i react-type-animation
15 | ```
16 |
17 |
18 | ```bash copy
19 | pnpm i react-type-animation
20 | ```
21 |
22 |
23 | ```bash copy
24 | yarn add react-type-animation
25 | ```
26 |
27 |
28 |
29 | ### Basic Usage
30 |
31 | ```js copy
32 | import { TypeAnimation } from 'react-type-animation';
33 |
34 | const ExampleComponent = () => {
35 | return (
36 | {
44 | console.log('Sequence completed');
45 | },
46 | ]}
47 | wrapper="span"
48 | cursor={true}
49 | repeat={Infinity}
50 | style={{ fontSize: '2em', display: 'inline-block' }}
51 | />
52 | );
53 | };
54 | ```
55 |
56 |
57 |
58 | ## Custom Props & Options
59 |
60 | See [Options →](/options)
61 |
62 | ## Examples
63 |
64 | See [Examples →](/examples)
65 |
66 | ## Migrating to v3
67 |
68 | From v3.x onwards, the default wrapper is `` instead of `
`. **To migrate**, add a `display: inline-block/block` css rule or `wrapper="div"` to all `` occurances with unspecified wrapper - or leave unchanged if you don't experience any new layout issues.
69 |
70 | ## Important Usage Notes
71 |
72 | ### Immutability
73 |
74 | Due to the nature of the animation, this component is **permanently memoized**, which means that the `` component **never re-renders unless you hard-reload the page**, and hence **props changes will not be reflected**.
75 |
76 |
77 | Note: You can still dynamically manipulate the styles of the animation via
78 | `ref` or `state` as shown in the
79 | [examples](/examples#manipulation-via-css-classes).
80 |
81 |
82 | Here is an example which shows that you cannot render dynamic prop-values:
83 |
84 | ```jsx copy
85 | const [counter, setCounter] = useState(0)
86 | setCounter(++counter), '']}
88 | repeat={Infinity}
89 | />
90 | ```
91 |
92 | **Renders**:
95 |
96 | In the example above, `counter` will always render as "0" within the animation and ignore state changes.
97 |
98 | ---
99 |
100 | ### Hot Reload NOT Supported
101 |
102 | Because the TypeAnimation component is memoized and **never** re-rendered (see above), yet Hot Reload attempts to re-render the component, **changes to the TypeAnimation component will not render until you hard-reload the page**.
103 |
104 | Hence, whenever you make changes to the TypeAnimation component, you unfortunately have to reload your page.
105 |
106 | ---
107 |
108 | ### Pure Text Limitation
109 |
110 | The Component is limited to **pure text** and cannot animate nested DOM elements:
111 |
112 | ❌ **Unsupported:**
113 | ` One Two
Three
`
114 |
115 | ✅ **Supported:**
116 | `One Two Three`
117 |
118 | ---
119 |
120 | ### Layout-shift
121 |
122 | As the typing animation progresses, the wrapper may expand and cause layout shift. See [here](/wrapper-css#preventing-layout-shift) for solutions.
123 |
124 | ---
125 |
126 | ### Changing the Wrapper Element
127 |
128 | It's recommended to **not change** the default `wrapper` prop (`span`) without a reason, as it may cause invalid HTML, hydration issues and semantical incorrectness as described [here](/wrapper-css).
129 |
--------------------------------------------------------------------------------
/example/pages/options.mdx:
--------------------------------------------------------------------------------
1 | import { OptionTable } from 'components/Table';
2 | import { Callout, Tab, Tabs, Steps } from 'nextra-theme-docs';
3 |
4 | # Options
5 |
6 | ## Component Props
7 |
8 | void | Promise)>',
13 | 'Animation sequence consisting of: [TEXT, DELAY-IN-MS, CALLBACK-FUNC]',
14 | "['One', 1000, 'Two', () => console.log('done typing!')]",
15 | '-',
16 | ],
17 | [
18 | 'wrapper',
19 | 'string',
20 | "HTML element name that wraps the typing animation. See 'Wrapper CSS' section for related info",
21 | "p,h2,div, strong",
22 | 'span',
23 | ],
24 | [
25 | 'repeat',
26 | 'number',
27 | 'Amount of animation repetitions. e.g. 0 = Animation will only be typed out once',
28 | "1, 3, Infinity",
29 | '0',
30 | ],
31 | [
32 | 'cursor',
33 | 'boolean',
34 | 'Whether to display default blinking cursor css-animation',
35 | "true, false",
36 | 'true',
37 | ],
38 | [
39 | 'preRenderFirstString',
40 | 'boolean',
41 | 'If set to true, the first string of your sequence will not be animated and initially (pre-)rendered',
42 | "true, false",
43 | 'false',
44 | ],
45 | [
46 | 'speed',
47 | '1,2,..,99 | {type: "keyStrokeDelayInMs", value: number}',
48 | 'Basic typing speed from 1-99 or exact keystroke delay in milseconds',
49 | "25, 50, 99, {type: 'keyStrokeDelayInMs', value: 250}",
50 | '40',
51 | ],
52 | [
53 | 'deletionSpeed',
54 | '1,2,..,99 | {type: "keyStrokeDelayInMs", value: number}',
55 | 'Basic deletion speed from 1-99 or exact keystroke delay in milseconds',
56 | "25, 50, 99, {type: 'keyStrokeDelayInMs', value: 250}",
57 | 'speed',
58 | ],
59 | [
60 | 'omitDeletionAnimation',
61 | 'boolean',
62 | 'If true, deletions will be instant and without animation',
63 | "true, false",
64 | 'false',
65 | ],
66 | [
67 | 'className',
68 | 'string',
69 | 'HTML class name applied to the wrapper of the typing animation',
70 | "some-class-name",
71 | '-',
72 | ],
73 | [
74 | 'style',
75 | 'object',
76 | 'JSX inline style object that will be applied to the wrapper of the typing animation',
77 | "{fontSize: '2em'}",
78 | '-',
79 | ],
80 | [
81 | 'ref',
82 | 'HTMLElement | null',
83 | 'A React ref that will be passed to the wrapper of the typing animation',
84 | "-",
85 | '-',
86 | ],
87 | [
88 | 'splitter',
89 | '(text: string) => Array',
90 | 'Custom string splitter, e.g for typing complex characters, such as those handled by the npm package "grapheme-splitter"',
91 | '(str) => new GraphemeSplitter().splitGraphemes(str)',
92 | `(str) => [...str]`,
93 | ]
94 | ]}
95 |
96 | />
97 |
98 | ### Props Examples
99 |
100 | See [all examples](/examples) to see all props in usage.
101 |
102 | - `ref`, `className` see [here](/examples#manipulation-via-css-classes).
103 | - `speed`, `deletionSpeed` see [here](/examples#custom-speed).
104 | - `preRenderFirstString` see [here](/examples#initially-pre-rendered).
105 | - `splitter` see [here](/examples#custom-splitter)
106 |
107 | ## Custom Cursor Animation
108 |
109 | If you wish to apply a custom cursor animation, set the `cursor` prop to `false` and set a custom `className` prop to the `` component with your own css styles.
110 |
111 |
112 |
113 |
114 |
115 | ```css filename="yourCssModule.module.css"
116 | .type::after {
117 | content: '|';
118 | animation: cursor 1.1s infinite step-start;
119 | }
120 |
121 | @keyframes cursor {
122 | 50% {
123 | opacity: 0;
124 | }
125 | }
126 | ```
127 |
128 | ```tsx
129 | import styles from './yourCssModule.module.css';
130 |
131 | ;
136 | ```
137 |
138 |
139 |
140 |
141 |
142 | ```css filename="yourGlobalCssFile.css"
143 | .type::after {
144 | content: '|';
145 | animation: cursor 1.1s infinite step-start;
146 | }
147 |
148 | @keyframes cursor {
149 | 50% {
150 | opacity: 0;
151 | }
152 | }
153 | ```
154 |
155 | ```tsx
156 | import './yourGlobalCssFile.css';
157 |
158 | ;
163 | ```
164 |
165 |
166 |
167 |
168 | ```tsx
169 | <>
170 |
175 |
186 | >
187 | ```
188 |
189 |
190 |
191 |
192 | ### Stop cursor animation
193 |
194 | If you would like the cursor to stop being displayed at a specific sequence step, see [here](/examples#manipulation-via-css-classes).
195 |
--------------------------------------------------------------------------------
/example/pages/wrapper-css.mdx:
--------------------------------------------------------------------------------
1 | import {
2 | SpanCollapsingExample,
3 | DisplayBlockCollapsingExample,
4 | WordBreakExample,
5 | } from '../components/examples';
6 | import { Callout } from 'nextra-theme-docs';
7 |
8 | # Wrapper & CSS tips
9 |
10 | ## Recommended Wrapper
11 |
12 | As mentioned in the [props](/options#component-props) section, the default wrapper for the `` component is a ``.
13 |
14 | You should mostly stick to the default value of `span` for the `wrapper` prop because:
15 |
16 | 1. The `span` element is the semantically correct element for a typing animation
17 | 2. The `span` element can appear as a child of many different HTML elements which should prevent you from running into hard to debug errors.
18 |
19 |
20 | **Be careful with custom wrappers:** if you set `wrapper='div'` but, for example, accidentally use the `` component as a child of a `
`, this would generate invalid HTML and cause React hydration errors if used with _SSR_ with Next.js or similar.
21 |