├── .gitignore ├── LICENSE ├── README.md └── contents ├── accept-any-numbers-of-parameters.mdx ├── access-local-webpack-dev-servers-from-external-devices.mdx ├── access-the-selected-node-in-the-console.mdx ├── add-a-line-break-between-inline-elements.mdx ├── add-a-subject-to-a-mailto-link.mdx ├── add-an-icon-to-external-links.mdx ├── add-keyboard-shortcuts.mdx ├── always-pass-the-radix-to-parseint.mdx ├── always-put-the-footer-at-the-bottom.mdx ├── always-use-noopener-or-noreferrer-for-links-opened-in-new-tabs.mdx ├── append-leading-zeros-to-ordered-list-items.mdx ├── avoid-boolean-parameters.mdx ├── avoid-invisible-text-when-loading-a-font.mdx ├── avoid-skipping-heading-levels.mdx ├── avoid-to-use-colons-and-periods-in-the-id-attribute.mdx ├── avoid-using-css-import.mdx ├── avoid-using-default-exports.mdx ├── avoid-using-multiple-h1-tags-per-page.mdx ├── avoid-using-the-b-i-s-and-u-tags.mdx ├── capture-a-screenshot-without-shadow-on-macos.mdx ├── center-an-element-vertically-and-horizontally.mdx ├── check-if-the-browser-supports-for-an-element-attribute.mdx ├── checkout-the-previous-branch.mdx ├── combine-google-font-requests.mdx ├── combine-styles-with-the-is-pseudo-class-selector.mdx ├── compose-multiple-react-providers.mdx ├── conditional-logging-in-the-console.mdx ├── convert-string-to-number.mdx ├── copy-a-long-variable-from-the-console.mdx ├── copy-screenshots-to-the-clipboard-on-macos.mdx ├── copy-the-base64-data-of-an-image.mdx ├── copy-the-full-path-of-a-file-on-macos.mdx ├── copy-variables-from-the-browser-console-to-the-clipboard.mdx ├── count-how-many-times-a-function-has-been-called.mdx ├── create-a-big-file-on-linux.mdx ├── create-a-custom-emoji-cursor.mdx ├── create-a-descending-list-of-numbered-items.mdx ├── create-a-download-link.mdx ├── create-a-file-of-any-size.mdx ├── create-a-function-that-accepts-a-single-parameter.mdx ├── create-a-line-on-sides-heading.mdx ├── create-a-multiline-strings.mdx ├── create-a-polyfill.mdx ├── create-an-array-with-conditional-elements.mdx ├── create-an-autocomplete-list-with-the-datalist-element.mdx ├── create-an-object-with-dynamic-keys.mdx ├── create-an-one-time-event-handler.mdx ├── create-shapes-with-the-clip-path-property.mdx ├── disable-all-fields-of-a-form.mdx ├── display-links-in-the-print-mode.mdx ├── do-not-add-custom-methods-to-primitive-objects.mdx ├── do-not-mix-styles-of-an-element-with-its-container.mdx ├── do-not-use-magic-numbers-when-manipulating-strings.mdx ├── do-not-use-submit-to-name-a-submit-button.mdx ├── early-return.mdx ├── enforce-required-parameters.mdx ├── escape-css-class-names.mdx ├── filter-file-types-of-a-file-input.mdx ├── find-scrollable-elements.mdx ├── find-the-root-npm-package-to-update.mdx ├── fold-css-declarations-with-region-markers.mdx ├── force-the-browsers-to-download-new-favicon.mdx ├── format-a-list.mdx ├── format-a-number-as-a-currency-string.mdx ├── get-characters-of-a-string.mdx ├── get-rid-of-escaping-quotes-with-template-literal.mdx ├── get-the-current-timestamp.mdx ├── go-to-the-previous-directory.mdx ├── hide-an-element-with-chrome-devtools.mdx ├── highlight-text-with-the-mark-element.mdx ├── ignore-case-sensitivity-in-a-css-attribute-selector.mdx ├── ignore-items-from-array-destructuring.mdx ├── include-properties-conditionally.mdx ├── increase-or-decrease-css-values-with-chrome-devtools.mdx ├── indicate-img-elements-that-miss-alt-attribute.mdx ├── insert-a-link-into-a-markdown-editor-quickly.mdx ├── inspect-an-element-shown-on-hover.mdx ├── keep-the-calculation-of-a-magic-number.mdx ├── lazy-loading-images-with-the-loading-attribute.mdx ├── list-branches-sorted-by-most-recent-commit-date.mdx ├── load-given-characters-in-a-google-font-request.mdx ├── locate-an-element-with-a-given-selector.mdx ├── log-a-value-to-the-console.mdx ├── log-a-variable-in-an-arrow-function.mdx ├── log-a-variable-to-the-console-using-conditional-breakpoints.mdx ├── log-an-array-to-the-console.mdx ├── log-the-full-object-in-nodejs.mdx ├── make-a-table-with-equal-column-widths.mdx ├── manage-multiple-boolean-flags.mdx ├── merge-different-arrays.mdx ├── move-the-cursor-to-any-position-in-a-macos-command.mdx ├── move-the-screenshot-area-on-macos.mdx ├── number-headings-and-subheadings-automatically.mdx ├── omit-properties-of-a-svelte-component.mdx ├── omit-values-of-html-boolean-attributes.mdx ├── open-a-package-s-homepage-or-repo.mdx ├── open-all-links-in-new-tabs.mdx ├── override-the-behavior-of-instanceof.mdx ├── pass-an-array-as-function-arguments.mdx ├── pass-specified-environments-to-webpack.mdx ├── persist-console-logs-between-page-refreshes.mdx ├── pick-given-properties-from-a-json-representation.mdx ├── pick-the-first-and-last-items-of-an-array.mdx ├── prefix-class-name-with-js-to-manipulate-with-javascript.mdx ├── pretty-format-json.mdx ├── prevent-a-string-from-being-escaped.mdx ├── prevent-anchor-links-from-disappearing-behind-a-sticky-header.mdx ├── prevent-browsers-from-asking-to-translate.mdx ├── prevent-macos-from-going-to-sleep.mdx ├── prevent-scrolling-behind-a-fixed-header.mdx ├── prevent-the-default-behavior-with-jquery-event-handler.mdx ├── pure-collapsible-element.mdx ├── put-special-operators-at-the-beginning-of-a-function.mdx ├── quick-query-elements-in-the-console.mdx ├── quickly-insert-alternate-characters-while-typing.mdx ├── quickly-type-color-variables.mdx ├── remove-a-property-from-an-object.mdx ├── remove-the-border-from-the-last-navigation-item.mdx ├── replace-multiple-if-statements-with-a-lookup-table.mdx ├── replace-multiple-if-statements-with-a-single-switch-statement.mdx ├── return-an-object-in-an-arrow-function-quickly.mdx ├── reuse-the-current-color.mdx ├── run-the-last-command-as-the-root-user.mdx ├── save-a-few-bytes-when-checking-the-existence-of-module.mdx ├── select-a-folder-to-upload.mdx ├── select-the-direct-children-of-an-element.mdx ├── separate-list-items.mdx ├── set-a-numbering-type-for-a-list-element.mdx ├── set-content-for-an-empty-link.mdx ├── set-git-configuration-conditionally.mdx ├── share-recommendation-visual-studio-code-extensions.mdx ├── shorten-codes-with-the-comma-operator.mdx ├── shorten-import-paths-in-typescript.mdx ├── shorten-import-paths-in-webpack.mdx ├── show-a-placeholder-for-an-empty-list.mdx ├── show-the-first-letter-only.mdx ├── skip-questions-when-creating-a-package-json-file.mdx ├── smooth-scrolling-with-pure-css.mdx ├── specify-the-doctype.mdx ├── start-a-simple-web-server-on-macos.mdx ├── style-broken-images.mdx ├── style-index-numbers-of-list-items.mdx ├── style-list-items-with-special-characters.mdx ├── swap-two-variables.mdx ├── toggle-hidden-files-on-macos.mdx ├── track-the-focused-element-with-chrome-devtools.mdx ├── transform-values-from-a-json-representation.mdx ├── trim-the-spaces-before-parsing-a-number.mdx ├── truncate-long-text.mdx ├── unpack-a-property-of-an-object-with-different-name.mdx ├── use-an-underscore-to-name-unused-argument.mdx ├── use-array-includes-for-multiple-conditionals.mdx ├── use-css-fallback-properties.mdx ├── use-documentfragments-when-adding-a-big-list-of-elements.mdx ├── use-multiple-ssh-keys-for-different-github-accounts.mdx ├── use-negative-nth-child-and-nth-last-child.mdx ├── use-short-circuits-conditionals.mdx ├── use-string-literals-for-the-typescript-enum-values.mdx ├── use-template-literal-to-concatenate-strings.mdx ├── use-the-datetime-attribute-when-displaying-dates-times.mdx ├── use-the-strict-equality-operator-when-comparing-variables.mdx ├── use-the-wbr-tags-to-represent-path.mdx ├── use-underscores-to-represent-a-number.mdx ├── validate-an-input-value-with-the-pattern-attribute.mdx ├── view-a-file-in-a-different-branch-without-switching-the-branch.mdx ├── view-print-stylesheets-with-chrome-devtools.mdx ├── visualize-elements-on-page-with-the-outline-style.mdx ├── watch-a-variable-value-with-live-expressions.mdx ├── wrap-arrow-function-body-in-parentheses.mdx └── write-css-rules-for-firefox-only.mdx /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 phuocng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Front-end Tips 2 | 3 | A series of super tiny, quick tips, tricks and best practices of front-end development. 4 | 5 | The series cover different topics: 6 | 7 | - CSS 8 | - HTML 9 | - JavaScript 10 | - TypeScript 11 | - Browser developer tools 12 | 13 | ## Contributing 14 | 15 | Pull requests are welcomed. To submit your favorite tip, please create a markdown file, and put it in the [contents](contents) folder. 16 | The content of markdown file has to look like 17 | 18 | ```md 19 | --- 20 | category: ___ 21 | created: '___' 22 | tags: ___ 23 | title: ___ 24 | --- 25 | 26 | The content of post 27 | ``` 28 | 29 | - `category`: Can be one of `Tip`, `Trick` or `Practice` 30 | - `created`: The date that post is created 31 | - `tags`: The list of topic(s), separated by a comma 32 | - `title`: Must match with the file name 33 | 34 | [Here](contents/convert-string-to-number.mdx) is an example. 35 | 36 | ## About 37 | 38 | This project is developed by _Nguyen Huu Phuoc_. I love building products and sharing knowledge. 39 | 40 | Be my friend on 41 | 42 | - [Twitter](https://twitter.com/nghuuphuoc) 43 | - [Github](https://github.com/phuocng) 44 | -------------------------------------------------------------------------------- /contents/accept-any-numbers-of-parameters.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | created: '2021-03-01' 4 | tags: JavaScript 5 | title: Accept any numbers of parameters 6 | --- 7 | 8 | In the old days, we can use the `arguments` variable to get the dynamic parameters that are passed to a function. 9 | 10 | ```js 11 | const sum = function () { 12 | return Array.from(arguments).reduce((a, b) => a + b, 0); 13 | }; 14 | 15 | sum(1); // 1 16 | sum(1, 2); // 3 17 | sum(1, 2, 3); // 6 18 | sum(1, 2, 3, 4); // 10 19 | ``` 20 | 21 | However, most of us aren't aware about the existence of `arguments`. It has some drawbacks such as 22 | 23 | - It's not an array, so we usually have to convert it to array first in order to use `Array` methods 24 | - More importantly, it isn't available in arrow functions 25 | 26 | The ES6 rest parameter (`...`) provides an easier way to work with an unknown numbers of parameters. The `sum` function above can be [written](https://phuoc.ng/collection/1-loc/calculate-the-sum-of-arguments/) as following: 27 | 28 | ```js 29 | const sum = (...params) => params.reduce((a, b) => a + b, 0); 30 | 31 | sum(1, 2, 3, 4, 5); // 15 32 | ``` 33 | 34 | ## See also 35 | 36 | - [Create a function that accepts a single parameter](https://phuoc.ng/collection/tips/create-a-function-that-accepts-a-single-parameter/) 37 | - [Pass an array as function arguments](https://phuoc.ng/collection/tips/pass-an-array-as-function-arguments/) 38 | -------------------------------------------------------------------------------- /contents/access-local-webpack-dev-servers-from-external-devices.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | cover: /assets/tips/webpack-dev-server-host-option.png 4 | created: '2021-04-26' 5 | tags: Webpack 6 | title: Access local webpack dev servers from external devices 7 | --- 8 | 9 | Sometimes we would like to access a local development server externally. It happens when we want to see how our web application works on mobile phones. Or co-workers want to see how it looks on their browsers. 10 | 11 | The local server is available at `http://localhost:PORT` where `PORT` represents the port number that the server listens on. In order to make it accessible in the same network, we have to replace `localhost` with the IP address. 12 | 13 | Webpack dev server allows the server to be available externally via the `host` option: 14 | 15 | ```js 16 | // webpack.config.js 17 | module.exports = { 18 | ... 19 | devServer: { 20 | host: '0.0.0.0', 21 | port: 8001, 22 | ... 23 | }, 24 | }; 25 | ``` 26 | 27 | The `host` option can be passed to the command line interface as well: 28 | 29 | ```shell 30 | webpack serve --host 0.0.0.0 31 | ``` 32 | 33 | With the configurations above, we can access the server internally (`http://localhost:8001`) and externally (`http://THE-IP-ADDRESS:8001`). 34 | -------------------------------------------------------------------------------- /contents/access-the-selected-node-in-the-console.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | created: '2021-03-04' 4 | tags: DevTools 5 | title: Access the selected node in the Console 6 | --- 7 | 8 | When you select a node under the _Elements_ tab, Chrome DevTools adds `== $0` right after the node. 9 | 10 | In give us a hint that we can use the `$0` expression to access the selected node. 11 | You can invoke any [DOM APIs](https://phuoc.ng/collection/html-dom) from `$0`. 12 | 13 | ![Access selected node](/assets/tips/selected-node.png) 14 | 15 | > The DevTools remembers the last five selected nodes. In addition to `$0`, we can use `$1`, `$2`, `$3` and `$4` to access the last selected nodes. 16 | 17 | You also can right-click the node, and choose _Store as global variable_ from the context menu. DevTools creates a variable, `temp1` for example, to represent the selected node. 18 | Now you can use `temp1` to manage the node in the same way as `$0`. 19 | 20 | ![Store selected node](/assets/tips/store-selected-node.png) 21 | -------------------------------------------------------------------------------- /contents/add-a-line-break-between-inline-elements.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | created: '2021-03-05' 4 | tags: CSS 5 | title: Add a line break between inline elements 6 | --- 7 | 8 | It's a common scenario where we want to split a heading into multiple lines. For example, the heading is displayed continuously on a big screen. But on a small screen, it should breaks into different parts. 9 | 10 | Without using the `br` tag, we can construct the heading from various inline `span` elements. 11 | 12 | ```html 13 |

14 | Tip, tricks, best practices 15 | of front-end development 16 |

17 | ``` 18 | 19 | By using the `::after` pseudo element, we are able to add a line break after the first inline element: 20 | 21 | ```css 22 | .primary::after { 23 | content: '\A'; 24 | white-space: pre; 25 | } 26 | ``` 27 | 28 | Where `\A` represents the line break character. 29 | 30 | 31 | ```html 32 |

33 | Tip, tricks, best practices 34 | of front-end development 35 |

36 | ``` 37 | 38 | ```css 39 | .demo__heading { 40 | font-weight: 500; 41 | text-align: center; 42 | } 43 | .demo__heading--primary::after { 44 | content: '\\A'; 45 | white-space: pre; 46 | } 47 | ``` 48 |
49 | -------------------------------------------------------------------------------- /contents/add-a-subject-to-a-mailto-link.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | created: '2021-03-25' 4 | tags: HTML 5 | title: Add a subject to a mailto link 6 | --- 7 | 8 | We already know that when users click a mailto link (` 14 | ``` 15 | 16 | In addition to the `subject` parameter, there are also `cc`, `bcc` and `body` parameters. They'll be filled in the default email application if specified. 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | It's worth noting that the `subject` and `body` parameters must replace the spaces with `%20`. There are some [online tool](https://mailtolinkgenerator.com) to [generate](https://mailtolink.me) the final mailto link for us. 23 | 24 | > If you want to use multiple email addresses in the `cc` and `bcc` parameters, then separate them with commas (`,`) 25 | -------------------------------------------------------------------------------- /contents/add-an-icon-to-external-links.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | created: '2021-02-23' 4 | tags: CSS 5 | title: Add an icon to external links 6 | --- 7 | 8 | If you want to add an icon to `a` element that links to an external website, then you can depend on the `href` or `[target="_blank"]` attribute. 9 | 10 | ## The target attribute 11 | 12 | ```css 13 | a[target='_blank'] { 14 | align-items: center; 15 | display: flex; 16 | } 17 | a[target='_blank']:after { 18 | /* The icon can be a SVG or image file */ 19 | content: url(/link/to/icon.svg); 20 | margin-left: 0.25rem; 21 | } 22 | ``` 23 | 24 | The `content` property can be a string that appends to the link: 25 | 26 | ```css 27 | a[target='_blank']:after { 28 | content: ' (external)'; 29 | } 30 | ``` 31 | 32 | Using an icon font such as [Font Awesome](https://fontawesome.com) is another option: 33 | 34 | ```css 35 | a[target='_blank']:after { 36 | content: ' \f08e'; 37 | font-family: 'FontAwesome'; 38 | } 39 | ``` 40 | 41 | ## The href attribute 42 | 43 | This approach relies on the `href` attribute. A link is treated as external if 44 | 45 | - It doesn't match with the domain of website 46 | - It isn't an anchor (doesn't start with `#`) 47 | - It doesn't start with `/` 48 | 49 | It's up to you to define more conditions here. But with the set of conditions above, the `:after` looks like 50 | 51 | ```css 52 | a:not([href*='domain.com']):not([href^='#']):not([href^='/']):after { 53 | /* Set the `content` property as mentioned in the first approach */ 54 | } 55 | ``` 56 | 57 | 58 | ```html 59 |
60 | GitHub 63 |
64 |
65 | GitHub 68 |
69 | ``` 70 | 71 | ```css 72 | .demo__link { 73 | align-items: center; 74 | display: flex; 75 | } 76 | .demo__link--icon[target='_blank']:after { 77 | content: url(/assets/tips/link.svg); 78 | margin-left: 0.25rem; 79 | } 80 | .demo__link--text[target='_blank']:after { 81 | content: ' (external)'; 82 | margin-left: 0.25rem; 83 | } 84 | ``` 85 |
86 | 87 | ## See also 88 | 89 | - [Ignore case sensitivity in a CSS attribute selector](https://phuoc.ng/collection/tips/ignore-case-sensitivity-in-a-css-attribute-selector/) 90 | -------------------------------------------------------------------------------- /contents/add-keyboard-shortcuts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Trick 3 | created: '2021-03-14' 4 | tags: HTML 5 | title: Add keyboard shortcuts 6 | --- 7 | 8 | By using the `accesskey` attribute, we can specify a shortcut key for the user to activate or focus on an element. The value of the attribute has to be a single character. 9 | 10 | It's worth noting that each browser provides a different combination of keys to access the shortcut. 11 | 12 | | Browser | macOS | Windows | Linux | 13 | | ------- | -------------------- | --------------------- | --------------------- | 14 | | Chrome | `alt` + `ctrl` + key | `alt` + key | `alt` + key | 15 | | Firefox | `alt` + `ctrl` + key | `alt` + `shift` + key | `alt` + `shift` + key | 16 | | Safari | `alt` + `ctrl` + key | n/a | n/a | 17 | 18 | In the following sample code, pressing the combination `alt` + `ctrl` + `e` on Chrome macOS will trigger the button's `click` event. 19 | 20 | ```html 21 | 22 | ``` 23 | -------------------------------------------------------------------------------- /contents/always-pass-the-radix-to-parseint.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Practice 3 | created: '2021-02-23' 4 | tags: JavaScript 5 | title: Always pass the radix to parseInt 6 | --- 7 | 8 | The `parseInt` method takes two parameters: 9 | 10 | ```js 11 | parseInt(value, radix); 12 | ``` 13 | 14 | The second parameter specifies the current numeral system. In the case it's not specified, then it will be set automatically based on the value. 15 | If the value starts with `0x` or `0X`, then the radix is 16 (hexadecimal). In other cases, the radix is 10 (decimal). 16 | 17 | In the older versions of JavaScript, if the string starts with `0` then the radix is set as 8 (octal). 18 | 19 | ```js 20 | parseInt('0xF'); // 15 21 | parseInt('0XF'); // 15 22 | parseInt('0xF', 16); // 15 23 | 24 | parseInt('0xF', 10); // 0 25 | ``` 26 | 27 | Since the method could be implemented differently in different versions of JavaScript and browsers, it's recommended to pass the radix number. 28 | -------------------------------------------------------------------------------- /contents/always-put-the-footer-at-the-bottom.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | created: '2021-03-06' 4 | tags: CSS 5 | title: Always put the footer at the bottom 6 | --- 7 | 8 | Assume that our page is organized by three parts: the header, main content and footer. It's a common layout that the footer is always displayed at the bottom no matter how long the main content is. 9 | 10 | ```html 11 | 12 |
13 |
...
14 |
...
15 | 16 |
17 | 18 | ``` 19 | 20 | Using CSS flexbox, the layout can be implemented as following: 21 | 22 | ```css 23 | .container { 24 | display: flex; 25 | flex-direction: column; 26 | min-height: 100vh; 27 | } 28 | 29 | header, 30 | footer { 31 | flex-shrink: 0; 32 | } 33 | 34 | main { 35 | flex-grow: 1; 36 | } 37 | ``` 38 | 39 | Setting `flex-grow: 1` to the main content will make it take the available spaces. The footer then [sticks at the bottom](https://phuoc.ng/collection/css-layout/sticky-footer/). 40 | -------------------------------------------------------------------------------- /contents/always-use-noopener-or-noreferrer-for-links-opened-in-new-tabs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Practice 3 | created: '2021-03-16' 4 | tags: HTML 5 | title: Always use noopener or noreferrer for links opened in new tabs 6 | --- 7 | 8 | In order to open a link in a new tab, we use the `target="_blank"` attribute. However, it can lead to some issues if you aren't aware of them. 9 | 10 | First, the newly opened tab uses the same process with the opener one. Hence, it can slow down your page. 11 | More importantly, the new tab is able to access the `window` object of the opener page via the `window.opener` object. Imagine that the new tab executes the following code: 12 | 13 | ```js 14 | window.opener.location = 'http://fake.website.here'; 15 | ``` 16 | 17 | As the code implies, it redirects the original tab to a fake website. What happens if the fake website has the same UI as the real one? Since users already opened it, they may not realize that the website they are looking at isn't real. 18 | 19 | Adding `rel="noopener"` fixes the issues. 20 | 21 | It's good to know that there is the `rel="noreferrer"` attribute. It not only fixes the issues that `noopener` does, but also prevents the `Referer` header from being sent to the new tab. 22 | 23 | ```html 24 | 25 | ... 26 | 27 | 28 | ... 29 | 30 | 31 | ... 32 | ... 33 | ``` 34 | 35 | > Some modern browsers, such as Chrome 88+, automatically adds the `noopener` behavior if it's missing. 36 | > However, it's still recommended to add `rel="noopener"` or `rel="noreferrer"` to avoid the security and performance issues in old legacy browsers. 37 | -------------------------------------------------------------------------------- /contents/append-leading-zeros-to-ordered-list-items.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Tip 3 | cover: /assets/tips/append-leading-zeros.png 4 | created: '2021-03-16' 5 | tags: CSS 6 | title: Append leading zeros to ordered list items 7 | --- 8 | 9 | Setting the `list-style-type` property to the below value will append zero number to items of an ordered list (`ol`): 10 | 11 | ```css 12 | ol { 13 | list-style-type: decimal-leading-zero; 14 | } 15 | ``` 16 | 17 | However, it only has effect with the items whose indices are less than 10. It means that if our list has more than 100 items, then they will be prefixed as following: 18 | 19 | ```shell 20 | 01. Item 21 | 02. Item 22 | ... 23 | 09. Item 24 | 10. Item 25 | ... 26 | 99. Item 27 | 100. Item 28 | ... 29 | ``` 30 | 31 | In order to fix that issue, we can use the CSS counter. Each item holds the current value of the counter which is incremented by one in the next item: 32 | 33 | ```css 34 | ol { 35 | counter-reset: items; 36 | list-style-type: none; 37 | } 38 | li { 39 | counter-increment: items; 40 | } 41 | ``` 42 | 43 | To prefix an item with its associate counter value, the `::before` pseudo element comes to the rescue. 44 | 45 | ```css 46 | li:before { 47 | content: '00' counter(items) '. '; 48 | } 49 | li:nth-child(n + 10)::before { 50 | content: '0' counter(items) '. '; 51 | } 52 | li:nth-child(n + 100)::before { 53 | content: counter(items) '. '; 54 | } 55 | ``` 56 | 57 | The `:nth-child(n+10)` selector indicates the items whose indices are greater or equal to 10. It will override the styles applied for `li::before` elements. 58 | In the same way, `:nth-child(n+100)` overrides the styles of `:nth-child(n+10)`. 59 | 60 | ## See also 61 | 62 | - [Create a descending list of numbered items](https://phuoc.ng/collection/tips/create-a-descending-list-of-numbered-items/) 63 | - [Number headings and subheadings automatically](https://phuoc.ng/collection/tips/number-headings-and-subheadings-automatically/) 64 | - [Set a numbering type for a list element](https://phuoc.ng/collection/tips/set-a-numbering-type-for-a-list-element/) 65 | - [Style index numbers of list items](https://phuoc.ng/collection/tips/style-index-numbers-of-list-items/) 66 | - [Style list items with special characters](https://phuoc.ng/collection/tips/style-list-items-with-special-characters/) 67 | - [Use negative nth-child and nth-last-child](https://phuoc.ng/collection/tips/use-negative-nth-child-and-nth-last-child/) 68 | -------------------------------------------------------------------------------- /contents/avoid-boolean-parameters.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Practice 3 | cover: /assets/tips/avoid-boolean-parameters.png 4 | created: '2021-05-13' 5 | tags: JavaScript 6 | title: Avoid boolean parameters 7 | --- 8 | 9 | Let's consider a situation where we have a function that writes a string to a file. It allows user to append the content to file, or override the content via the `override` parameter: 10 | 11 | ```js 12 | const writeToFile = (content: string, file: string, override: boolean) => { 13 | ... 14 | }; 15 | ``` 16 | 17 | With that signature, the function will be invoked as following 18 | 19 | ```js 20 | // Append the content to file 21 | writeToFile(content, file, true); 22 | 23 | // Override the file 24 | writeToFile(content, file, false); 25 | ``` 26 | 27 | If you are not the one who creates the function, you have to question what the boolean value represents until looking at the implementation. 28 | 29 | It is worse if the function has a lot of boolean flags. Using boolean flags makes the core harder to read and maintain. 30 | 31 | There are a few ways to get rid of the issue. 32 | 33 | ## Provide explicit methods 34 | 35 | ```js 36 | appendToFile(content, file); 37 | overrideFile(content, file); 38 | ``` 39 | 40 | ## Use an object parameter 41 | 42 | ```js 43 | writeToFile(content, file, { override }); 44 | ``` 45 | 46 | ## Use an enum 47 | 48 | If you're using TypeScript, then you can use `enum` to represent the possible values of a boolean flag. 49 | 50 | ```js 51 | enum SaveMode { 52 | Append, 53 | Override, 54 | } 55 | 56 | writeToFile(content, file, mode: SaveMode); 57 | ``` 58 | 59 | It's confident for consumers to call the method: 60 | 61 | ```js 62 | writeToFile(content, file, SaveMode.Append); 63 | 64 | // Or 65 | writeToFile(content, file, SaveMode.Override); 66 | ``` 67 | 68 | ## See also 69 | 70 | - [Manage multiple boolean flags](https://phuoc.ng/collection/tips/manage-multiple-boolean-flags/) 71 | -------------------------------------------------------------------------------- /contents/avoid-invisible-text-when-loading-a-font.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Practice 3 | created: '2021-03-25' 4 | tags: CSS, Google Font 5 | title: Avoid invisible text when loading a font 6 | --- 7 | 8 | It takes time to load a big font. Most browsers will hide texts until the font is loaded completely. This problem is known as _flash of invisible text_ (FOIT). 9 | 10 | We can prevent it from happening by asking the browser to use the system font while the custom font is still being loaded. Once the font is loaded, it will replace the system font used earlier. 11 | This phrase is also known as _flash of unstyled text_ (FOUT). 12 | 13 | In order to archive it, we can use the `font-display` style: 14 | 15 | ```css 16 | @font-face { 17 | font-family: 'Roboto'; 18 | font-display: swap; 19 | } 20 | ``` 21 | 22 | If you are using [Google fonts](https://fonts.google.com), then putting the `display=swap` parameter is the equivalent way: 23 | 24 | ```html 25 | 26 | ``` 27 | 28 | ## See also 29 | 30 | - [Combine Google font requests](https://phuoc.ng/collection/tips/combine-google-font-requests/) 31 | - [Load given characters in a Google font request](https://phuoc.ng/collection/tips/load-given-characters-in-a-google-font-request/) 32 | -------------------------------------------------------------------------------- /contents/avoid-skipping-heading-levels.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Practice 3 | cover: /assets/tips/avoid-skip-headings.png 4 | created: '2021-05-13' 5 | tags: Accessibility, HTML 6 | title: Avoid skipping heading levels 7 | --- 8 | 9 | It's common to use the heading elements, `

` to `

`, to represent the heading of sections. 10 | The `

` tag is often used in the highest section, whereas the `

`, `

`, ... tags are used in the lower section. 11 | 12 | Heading elements are used not only because of their semantic meanings but also by screen reading tools. Those tools determine the content of the page based on the headings, and allow users to navigate between sections via the headings. 13 | 14 | It's recommended to keep the heading elements in the order, so the users won't be confused that there's a missing heading or section while navigating on the page. 15 | 16 | ```html 17 | 18 |

Main heading

19 |

Heading level 2

20 |

Heading level 3

21 | 22 | 23 |

Main heading

24 |

Heading level 2

25 |

Heading level 3

26 | ``` 27 | 28 | ## See also 29 | 30 | - [Avoid using multiple `

` tags per page](https://phuoc.ng/collection/tips/avoid-using-multiple-h1-tags-per-page/) 31 | -------------------------------------------------------------------------------- /contents/avoid-to-use-colons-and-periods-in-the-id-attribute.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: Practice 3 | created: '2021-02-23' 4 | tags: DOM, HTML 5 | title: Avoid to use colons and periods in the id attribute 6 | --- 7 | 8 | According to the [HTML specifications](https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute), a valid `id` can consist of almost characters except [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace). 9 | Assume that we have an element representing an user's email address: 10 | 11 | ```html 12 |
13 | ``` 14 | 15 | In order to access the element, the `getElementById()` method accepts all of three ways passing the `id`: 16 | 17 | ```js 18 | // They return the same element 19 | document.getElementById('user.email'); 20 | document.getElementById('user\\.email'); 21 | document.getElementById('user\\\\.email'); 22 | ``` 23 | 24 | But these methods return different results if you are using [jQuery](https://jquery.com) library: 25 | 26 | ```js 27 | // Function // Returned element 28 | $('#user.email'); //