├── .editorconfig ├── .github └── workflows │ └── pages.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── docs ├── .vitepress │ ├── components │ │ └── live-example.vue │ ├── config.mjs │ └── theme │ │ ├── index.js │ │ └── styles.css ├── advanced │ └── understanding-the-vue-source-code.md ├── component-libraries.md ├── components │ ├── accordion.md │ ├── accordion │ │ ├── accordion-panel.vue │ │ ├── accordion.vue │ │ └── example.vue │ ├── checkbox.md │ ├── checkbox │ │ ├── checkbox-without-input.vue │ │ └── checkbox.vue │ ├── radio-group.md │ ├── radio-group │ │ ├── radio-group.vue │ │ └── radio.vue │ ├── radio.md │ ├── radio │ │ ├── radio-without-input.vue │ │ └── radio.vue │ ├── tabs.md │ ├── tabs │ │ ├── example.vue │ │ ├── tab.vue │ │ └── tabs.vue │ ├── toggle-switch.md │ └── toggle-switch │ │ └── toggle-switch.vue ├── exercises │ ├── abbey-road.js │ ├── elements.js │ ├── index.md │ ├── minesweeper.md │ ├── minesweeper.vue │ ├── numbers-game.md │ ├── numbers-game.vue │ ├── platonic-solids.js │ ├── quiz-game.vue │ ├── quiz.md │ ├── seven-wonders.js │ ├── tic-tac-toe.md │ └── tic-tac-toe.vue ├── guides │ └── working-with-image-assets.md ├── index.md ├── patterns │ ├── computed-v-model.md │ ├── computed-v-model │ │ ├── proxy-example.vue │ │ └── user-edit-form.vue │ ├── coupled-components-with-provide-inject.md │ └── global-properties.md └── public │ └── images │ ├── cross.svg │ └── tick.svg ├── package.json └── pnpm-lock.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 2 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy to GitHub Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ['main'] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment 19 | concurrency: 20 | group: 'pages' 21 | cancel-in-progress: false 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | - name: Install pnpm 34 | uses: pnpm/action-setup@v4 35 | with: 36 | version: 9 37 | - name: Set up Node 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: 20 41 | cache: 'pnpm' 42 | - name: Install dependencies 43 | run: pnpm install 44 | - name: Build 45 | run: pnpm run build 46 | - name: Setup Pages 47 | uses: actions/configure-pages@v4 48 | - name: Upload artifact 49 | uses: actions/upload-pages-artifact@v3 50 | with: 51 | # Upload dist directory 52 | path: './dist' 53 | - name: Deploy to GitHub Pages 54 | id: deployment 55 | uses: actions/deploy-pages@v4 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /docs/.vitepress/cache 4 | /dist 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2024 skirtle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Examples 2 | 3 | Vue 3 examples deployed at [https://skirtles-code.github.io/vue-examples](https://skirtles-code.github.io/vue-examples). 4 | 5 | ## Suggestions 6 | 7 | If you have a suggestion for improving the site please [create an issue](https://github.com/skirtles-code/vue-examples/issues). 8 | -------------------------------------------------------------------------------- /docs/.vitepress/components/live-example.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 49 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfigWithTheme } from 'vitepress' 2 | 3 | export default defineConfigWithTheme({ 4 | outDir: '../dist', 5 | cleanUrls: true, 6 | base: '/vue-examples', 7 | title: 'Vue Examples', 8 | lang: 'en-US', 9 | description: 'Examples of Vue patterns and basic components', 10 | appearance: false, 11 | 12 | transformHead({ page, siteData: { base } }) { 13 | if (page !== '404.md') { 14 | const canonicalUrl = `https://skirtles-code.github.io${base}${page}` 15 | .replace(/index\.md$/, '') 16 | .replace(/\.md$/, '') 17 | 18 | return [['link', { rel: 'canonical', href: canonicalUrl }]] 19 | } 20 | }, 21 | 22 | themeConfig: { 23 | search: { 24 | provider: 'local' 25 | }, 26 | 27 | socialLinks: [ 28 | { icon: 'github', link: 'https://github.com/skirtles-code/vue-examples' } 29 | ], 30 | 31 | sidebar: [ 32 | { 33 | text: 'Introduction', 34 | link: '/' 35 | }, { 36 | text: 'Component Libraries', 37 | link: '/component-libraries' 38 | }, { 39 | text: 'Example Components', 40 | items: [ 41 | { 42 | text: 'Checkbox', 43 | link: '/components/checkbox', 44 | }, { 45 | text: 'Radio', 46 | link: '/components/radio', 47 | }, { 48 | text: 'Toggle Switch', 49 | link: '/components/toggle-switch', 50 | }, { 51 | text: 'Radio Group', 52 | link: '/components/radio-group' 53 | }, { 54 | text: 'Accordion', 55 | link: '/components/accordion', 56 | }, { 57 | text: 'Tabs', 58 | link: '/components/tabs', 59 | } 60 | ] 61 | }, { 62 | text: 'Patterns', 63 | items: [ 64 | { 65 | text: 'Computed with v-model', 66 | link: '/patterns/computed-v-model' 67 | }, { 68 | text: 'Global Properties', 69 | link: '/patterns/global-properties' 70 | }, { 71 | text: 'Coupled Components with provide/inject', 72 | link: '/patterns/coupled-components-with-provide-inject' 73 | } 74 | ] 75 | }, { 76 | text: 'Guides', 77 | items: [ 78 | { 79 | text: 'Working with Image Assets', 80 | link: '/guides/working-with-image-assets' 81 | }, { 82 | text: 'Understanding the Vue Source Code', 83 | link: '/advanced/understanding-the-vue-source-code' 84 | } 85 | ] 86 | }, { 87 | text: 'Exercises', 88 | link: '/exercises/', 89 | items: [ 90 | { 91 | text: 'Tic-tac-toe', 92 | link: '/exercises/tic-tac-toe' 93 | }, { 94 | text: 'Quiz', 95 | link: '/exercises/quiz' 96 | }, { 97 | text: 'Minesweeper', 98 | link: '/exercises/minesweeper' 99 | }, { 100 | text: 'Numbers Game', 101 | link: '/exercises/numbers-game' 102 | } 103 | ] 104 | } 105 | ] 106 | } 107 | }) 108 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import './styles.css' 3 | import LiveExample from '../components/live-example.vue' 4 | 5 | export default { 6 | extends: DefaultTheme, 7 | enhanceApp({ app }) { 8 | app.component('live-example', LiveExample) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Switch the theme to green */ 3 | --vp-c-brand-1: var(--vp-c-green-1); 4 | --vp-c-brand-2: var(--vp-c-green-2); 5 | --vp-c-brand-3: var(--vp-c-green-3); 6 | --vp-c-brand-soft: var(--vp-c-green-soft); 7 | 8 | /* green-1 is too dark, so use green-2 instead */ 9 | --vp-home-hero-name-color: var(--vp-c-brand-2); 10 | 11 | /* Revert inline code to the old theme, so it doesn't look like a link */ 12 | --vp-code-color: #476582; 13 | 14 | /* Put borders on code and custom blocks */ 15 | --custom-code-block-border: var(--vp-c-divider); 16 | 17 | --vp-custom-block-danger-border: hsla(358, 75%, 44%, 0.4); 18 | --vp-custom-block-warning-border: hsla(32, 95%, 44%, 0.4); 19 | --vp-custom-block-tip-border: hsla(153, 25%, 44%, 0.4); 20 | --vp-custom-block-info-border: hsla(240, 10%, 64%, 0.4); 21 | } 22 | 23 | /* Borders on custom blocks in dark mode */ 24 | html:not(.dark) { 25 | --vp-custom-block-danger-bg: hsl(350, 81%, 96%); 26 | --vp-custom-block-warning-bg: hsl(45, 93%, 94%); 27 | --vp-custom-block-tip-bg: hsl(160, 81%, 98%); 28 | --vp-custom-block-info-bg: hsl(240, 6%, 97%); 29 | } 30 | 31 | .dark { 32 | /* Inline code for the dark theme */ 33 | --vp-code-color: #c9def1; 34 | } 35 | 36 | /* Apply a border to code blocks and code groups */ 37 | .vp-doc div[class*='language-'] { 38 | border: 1px solid var(--custom-code-block-border); 39 | } 40 | 41 | .vp-code-group > .tabs { 42 | border: 1px solid var(--custom-code-block-border); 43 | border-bottom: 0 none; 44 | } 45 | 46 | .vp-code-group > .blocks > div[class*='language-'] { 47 | border-top: 0 none; 48 | } 49 | 50 | /* Inline code in a custom block looks too much like a link */ 51 | .custom-block.info code, .custom-block.tip code { 52 | color: var(--vp-code-color); 53 | } 54 | 55 | .custom-block.info a > code, .custom-block.tip a > code { 56 | color: var(--vp-code-link-color); 57 | } 58 | 59 | /* green-1 is much too close to black in the sidebar */ 60 | html:not(.dark) .VPSidebar { 61 | --vp-c-brand-1: var(--vp-c-brand-3); 62 | } 63 | -------------------------------------------------------------------------------- /docs/advanced/understanding-the-vue-source-code.md: -------------------------------------------------------------------------------- 1 | # Understanding the Vue Source Code 2 | 3 | The Vue 3 source code can be found at . 4 | 5 | Jumping into the Vue source code to learn how Vue works seems like a great idea, until you try it. 6 | 7 | If you're new to Vue, or programming more generally, then you may be better off focusing your attention elsewhere. Writing applications is a much better way to get better at *using* Vue. Studying the Vue codebase is not required to use Vue. It can be really rewarding, but it isn't necessary. 8 | 9 | Vue is a large, open-source codebase. If you haven't studied anything similar then you'll be in for a bit of a shock. The quality of the code is not a problem, it's just a very steep learning curve trying to get a grasp on what it all does. 10 | 11 | The code is changing all the time, so any attempt to document it in any detail will quickly become obsolete. That said, I'm going to try to outline some general advice that might help you to get beyond the first hurdle. Much of this advice would be transferable to other codebases too, but I've tried to tailor it to Vue specifically. 12 | 13 | Before you jump in, it's worth considering whether you might be better off contributing in some other way: 14 | 15 | - Helping out on [Discord](https://chat.vuejs.org/) is a great way to contribute and learn, as you'll gain exposure to a wide range of use cases that you won't have seen in your own projects. It's one of the easiest ways to start getting involved and can help you to prepare for contributing in other ways. 16 | - The documentation can always be improved. If you're not comfortable writing in English then you could potentially help out with one of the translations. 17 | - There are dozens of other [official Vue repos](https://github.com/vuejs/), such as Vue Router, Pinia, VitePress, Vue Language Tools and Vue Devtools. Most of these are much smaller and easier to understand than Vue itself. They also tend to have a greater need for more maintainers. 18 | 19 | See for more ideas about how you might contribute. 20 | 21 | Right, back to the Vue 3 core codebase... 22 | 23 | ## Prerequisites 24 | 25 | Before you decide to press on, here's what you'll need to stand the best chance of understanding what's going on: 26 | 27 | 1. JavaScript knowledge is essential. TypeScript knowledge is a plus, but you can get a long way without it, so long as you understand enough to be able to ignore the types. 28 | 2. Make sure you know the documented API really well. If features like scoped slots or render functions are still a bit of a mystery then you aren't ready to start digging into the source. 29 | 3. Try to understand the different ways Vue is used. Do you know what the different builds do? See for a list of builds and for an explanation. 30 | 31 | ## Getting Started 32 | 33 | Be patient. Studying the code takes time. 34 | 35 | Expect to be confused a lot when you first start. This might feel very uncomfortable. Your instincts might be telling you that it's important to understand everything and not to move on until you do. That's usually a great attitude to have, but you'll need to suppress it. 36 | 37 | Start with a light skim through everything to get a feel for what's what. You'll need that mental scaffolding in place before you can start building up proper knowledge of how things work. 38 | 39 | Beyond an initial skim, trying to study the code without a specific objective in mind is unlikely to make much progress. 40 | 41 | Have a browse through the [Issues](https://github.com/vuejs/core/issues) and [PRs](https://github.com/vuejs/core/pulls) on GitHub (look at the closed/merged ones, not just the open ones). You can pick up a lot of the internal jargon that way. Looking through how past bugs were fixed will also give you something concrete to read and understand. Again, the objective isn't to understand everything, it's to pick up nuggets that you do understand that you can build on later. 42 | 43 | Have a look through the [changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md). Try to pick up some of the jargon. It's often difficult to understand what exactly a change did based on the brief description in the changelog, but if you see one that sounds interesting, click through to see what change was made. Maybe you'll understand some of it, maybe you won't. You might feel like you're not learning anything, but even if you don't understand what the code does you'll be picking up knowledge around the edges. A bit like learning a foreign language, exposure can help you to learn the accent even if you don't understand the meaning of what's being said. 44 | 45 | Stepping through the code in the debugger can be a really good way to study some features. Pick a specific feature and step into running code in your browser and walk through the code that way. That might not be viable for all features, so pick one that you can study that way, e.g. those that allow you to pass a function containing a `debugger` statement. 46 | 47 | ## Other Advice 48 | 49 | Tools like the [Template Explorer](https://template-explorer.vuejs.org/) and [SFC Playground](https://play.vuejs.org/) allow you to see how Vue's template compiler and SFC compiler transform application code. If you're specifically interested in learning more about those features then those tools can help you to understand what the compilers do, before you try to study how they do it. 50 | 51 | There are some really insightful videos of Evan speaking at conferences that might help to give a bit more background on the internals. I found this one useful, , but there are plenty of others that touch on different things. 52 | 53 | If you don't understand a particular bit of code, maybe try using **git blame** to see how that code came to be. You should be able to do that via the UI in either GitHub or your IDE. You'll often find that the original version of a file was much easier to understand and the weird and wonderful contortions were added later to address specific edge cases. Using blame to see what commits contributed to the current file can help to unpick those mysteries. 54 | 55 | Don't worry too much about the tooling or the build process. You should make sure you can build locally and run the tests, but there's no need to dig into the details of all the tooling. You can if you want, it's all useful knowledge, but it's unlikely to teach you much about how Vue works. 56 | 57 | Once you think you understand a part of the code, try changing it! Even if it's just some console logging to check you've understood correctly. 58 | 59 | Be patient. Make sure you've prepared yourself mentally for not understanding what's going on. Knowing how to study a codebase like this is a skill in itself and it can take time to get the hang of it. The key is striking a balance. Sometimes you need to dig deeper to unpick the mysteries. Other times you need to move on and not worry about how the magic is done. Gradually that balance will shift, but initially it'll all feel like magic. 60 | -------------------------------------------------------------------------------- /docs/component-libraries.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Popular Vue 3 component libraries 3 | --- 4 | # Component Libraries 5 | 6 | Listed below are some of the most widely used component libraries for **Vue 3**. 7 | 8 | ## Styled Vue components 9 | 10 | These libraries all provide large collections of ready-to-use Vue components, complete with styling. 11 | 12 | The monthly downloads should not be trusted as a way to judge the quality of a library. Specifically: 13 | * Some of these libraries are much older than others and will benefit from historical inertia. 14 | * Some used the same npm package name for the Vue 2 version of the library, while others did not. The final column of the table attempts to indicate whether the monthly downloads also include Vue 2. 15 | 16 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
NamedocsnpmVue 2?
Vuetify1docsnpm npm monthly downloadsTick Yes
Element Plusdocsnpm npm monthly downloadsnpm npm monthly downloads
PrimeVuedocsnpm npm monthly downloadsTick Yes
Quasardocsnpm npm monthly downloadsTick Yes
Ant Design Vuedocsnpm npm monthly downloadsTick Yes
Nuxt UI2docsnpm npm monthly downloadsCross No
Vant3docsnpm npm monthly downloadsTick Yes
Naive UIdocsnpm npm monthly downloadsCross No
bootstrap-vue-next4docsnpm npm monthly downloadsCross No
Flowbite Vue5docsnpm npm monthly downloadsCross No
Oruga UI6docsnpm npm monthly downloadsnpm npm monthly downloads
Vuestic UIdocsnpm npm monthly downloadsCross No
110 | 111 |
112 | 113 | **Notes:** 114 | 115 | 1. Vuetify 3 is compatible with Vue 3. Vuetify 3.0.0 was released at the end of October 2022 and is still missing some important features relative to earlier versions. 116 | 2. Nuxt UI can be used without Nuxt. 117 | 3. Vant targets mobile browsers and isn't generally suitable for desktop applications. 118 | 4. bootstrap-vue-next started out as bootstrap-vue-3, npm npm monthly downloads. It was an independent rewrite of BootstrapVue: docs, npm npm monthly downloads, which was a very popular Vue 2 component library. In late 2022, BootstrapVue announced work on Vue 3 compatibility, . In early 2023, bootstrap-vue-3 was renamed to bootstrap-vue-next. 119 | 5. Flowbite Vue provides Vue components built using Flowbite. Flowbite itself is conceptually similar to Bootstrap or Buefy and is framework-agnostic. 120 | 6. The Vue 2 library Buefy: docs, npm npm monthly downloads, combines Vue with Bulma. The lead maintainer of that project also maintains Oruga UI and recommends using Oruga as the successor to Buefy for Vue 3. 121 | 122 | ## Unstyled Vue components 123 | 124 | These libraries provide Vue components without styling. They can be used to build your own component library. 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
NamedocsnpmVue 2?
Headless UIdocsnpm npm monthly downloadsCross No
Reka UIdocsnpm npm monthly downloadsCross No
145 | 146 | Prior to version 2, Reka UI was known as Radix Vue: docs, npm npm monthly downloads. 147 | 148 | There are also collections of example Vue components that use these two libraries: 149 | 150 | * [Tailwind UI](https://tailwindui.com/) - Headless UI and Tailwind CSS. 151 | * [shadcn-vue](https://www.shadcn-vue.com/) - Reka UI. 152 | 153 | Nuxt UI is also built on Reka UI. 154 | 155 | ## CSS frameworks 156 | 157 | The CSS frameworks listed here are not specifically tied to Vue. 158 | 159 | daisyUI extends Tailwind CSS, adding utility classes for writing components. 160 | 161 | Flowbite is conceptually similar to Bootstrap or Bulma, but built using Tailwind CSS. 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 |
Namedocsnpm
Tailwind CSSdocsnpm npm monthly downloads
Bootstrapdocsnpm npm monthly downloads
Flowbitedocsnpm npm monthly downloads
daisyUIdocsnpm npm monthly downloads
Bulmadocsnpm npm monthly downloads
195 | 196 | All of these libraries can be used with Vue, but some of them include their own JavaScript code to add interactivity, which may clash with Vue. You'll likely need to implement that interactivity yourself instead, or use one of the Vue integrations listed earlier in [Styled Vue components](#styled-vue-components). 197 | -------------------------------------------------------------------------------- /docs/components/accordion.md: -------------------------------------------------------------------------------- 1 | 4 | # Accordion 5 | 6 | ## Accordion Example 7 | 8 | The accordion presented here is made up of two components. There is an outer `accordion` component that acts as a container, with `accordion-panel` components as children that can be expanded or collapsed. 9 | 10 | Usage might look something like this: 11 | 12 | <<< @/components/accordion/example.vue 13 | 14 | While the 3 children given in this example are static, they could also be created dynamically using `v-for` over a suitable array. 15 | 16 | Running this example we get: 17 | 18 | 19 | 20 | 21 | 22 | [SFC Playground](https://play.vuejs.org/#eNqlVE1v2zAM/StcekiK5aufA7ykW3cYdizQ9ZZDHUmOtcqSJ8lpgyD/fZRkO/bidOt2SSyKfHykHrnt3eb5eF2wXtSbGaJ5bsEwW+Q3C8mzXGkLX2LDyS0hSlOuJCRaZdAfT9pmB9E/EnIXSyaOxPm7Mng2CQQwNR4sy3IRW4YngNnSRY3iKswbD82j3Key3Ao2X/S+cm3solc6A/gzBB+ipGXSljiTTqC/y3LPEIs20tTUYT1KlEYXDlzC+bThAxDC2mxguwUOu93ea7bUiMITD/JuPm+jzCatLv1nId9Trpt1+PNb23Vwh9YGzd6wdyic49rbYuIsLyyjQ8i1WnPKhqBZArtST0E5CzmZwDclqAGbMuAUVOK/SKE10hYbYC/IkbKynoXEglALtXXuUAeyEOK0hHswDB7LnI9glWOSFZIT97DP3KYhQcpFiWkWsnQf9Ped0WzFjWW6P4TBKcxvYOu6FLIjzzncb7KlEgOfFpCFLbQMTlDTi+o2DAJIdTFex6JgMEdhcHo6DBiAbFcrwVzCEmgPVUV0Q8AncD2I8DME7mrMQlaltHB5AoNuNnufjuwuS3Vdyt3/4c8OW/HaKqB8DUTExqBk6z6jbGdGKAuTm9kEPdqq8yjGbrAnhqicUbSM69hAdIkHpiM4y1/AKIG9OCGEfHRXlBvE2USQCPbiLe5jRLlmxCKAex5RZNJfpYyvUhvBxXSaB+dnTm0awfllMGCJWJ4jczgN9Tp8bSS4/IF5h6Dkg8xUgYNJD+YhCAz1mBvsNmUJl+zOnQa+WD/yUfVCmv0ssBaUmdUFwxf3Gtrk6HFvNZerxstU0LUY8K09n07RNwK2tQiGpT6HTU3t/AiGQxiGRnmDveO/iCOsp0UPoupqTwbrqjZeKzhlMcoBYz4TwckTWgJp9MYl7fuHi7rSWh1e7uoKHeMrwKWimw6Vuo35Rr2Wa9s/3hukiQ/YgfIeuoGzWK+4HFmV+5Go40NfypGJydNK4xPREWZRODwn7Dq5ThJP5JWBqrzPyQW7mgZToY2z5Yrji2tvEyjaUTVPZ+NLbwzEIhgFVoDLl1LUaARXDZqu20dJJh8S8meSrpERxIVVrbxTn3mfXq2ZToR6rl0DgVpfnsTvWNjZqi5XfnMl7H4BQxxFKw==) 23 | 24 | The code for `accordion.vue`: 25 | 26 | <<< @/components/accordion/accordion.vue 27 | 28 | The corresponding `accordion-panel.vue` is: 29 | 30 | <<< @/components/accordion/accordion-panel.vue 31 | 32 | ## Vue Patterns 33 | 34 | See [Coupled Components with `provide`/`inject`](../patterns/coupled-components-with-provide-inject). 35 | 36 | 41 | 42 | ## Libraries 43 | 44 | Various libraries include an accordion component, or a component that can achieve a similar effect. These include: 45 | 46 | - Vuetify - [Expansion Panel](https://next.vuetifyjs.com/en/components/expansion-panels/) 47 | - Element Plus - [Collapse](https://element-plus.org/en-US/component/collapse.html) 48 | - Quasar - [Expansion Item](https://quasar.dev/vue-components/expansion-item) 49 | - Ant Design Vue - [Collapse](https://www.antdv.com/components/collapse) 50 | - Headless UI - [Disclosure](https://headlessui.dev/vue/disclosure) 51 | - PrimeVue - [Accordion](https://primefaces.org/primevue/accordion) 52 | - Naive UI - [Collapse](https://www.naiveui.com/en-US/os-theme/components/collapse) 53 | - Oruga - [Collapse](https://oruga.io/components/Collapse.html) 54 | -------------------------------------------------------------------------------- /docs/components/accordion/accordion-panel.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 56 | -------------------------------------------------------------------------------- /docs/components/accordion/accordion.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 30 | 31 | 40 | -------------------------------------------------------------------------------- /docs/components/accordion/example.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /docs/components/checkbox.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: 'deep' 3 | --- 4 | 11 | # Checkbox 12 | 13 | :::info 14 | This page has not yet been updated to use the `defineModel` macro, which was added in Vue 3.4. The techniques described here should still work, but in some cases it might be better to use `defineModel` instead. 15 | ::: 16 | 17 | ## Checkbox Examples 18 | 19 | We're going to look at two examples of a checkbox component. The first example wraps a native `` element to create the checkbox. The second example uses CSS to give the impression of a checkbox without using an ``. 20 | 21 | In both cases the example usage is much the same: 22 | 23 | ```vue 24 | 28 | 29 | 35 | ``` 36 | 37 | The important part is the two-way binding created using `v-model`. 38 | 39 | ### Checkbox with `` 40 | 41 | 42 | 43 |
Bound value: {{ checkboxValue }}
44 |
45 | 46 | [SFC Playground](https://play.vuejs.org/#eNp9ksFugzAMhl/FygUqteWOaKV12n2nXcYOlLhbNEiykLBNiHefk7SUdlVv2LH9f/7NwB60XvcOWc6KrjZCW+jQOr0tpWi1MhYGMHiAEQ5GtZBQaTI97apO1I8fWH/u1c+xYJ1dZP1saihlrWRnoT6mX6rGIWz86NQah4tSFlnUJ2UKLLa6qSxSBFDs/cjVqRn6Vas4NpuSXcwrGWSxXhvc7pSTHHr/kMMwXEmPY5H5Kq87abEl+wd/z5hatdpZ5FfunJbVRumOluR4EBKffZQOni/QBw4i8wkgI76cMMhz8HYsY9L+aqrYKdVgRVMBxlKOZNVJAFthp/lPFHTpa+I0p13ys0byNmsJLhDxZoKPSO9oIV2caawzMi6wPk8KCIGNrIBU4ndwCvnU6InSGwhLmBXPNrl3dC768EGfQhJqsGN2czr31Y+AvGSxN4vN89uOfw4jBwg=) 47 | 48 | <<< @/components/checkbox/checkbox.vue 49 | 50 | ### Checkbox without `` 51 | 52 | 53 | 54 |
Bound value: {{ checkboxValue }}
55 |
56 | 57 | [SFC Playground](https://play.vuejs.org/#eNqFUUuP2jAQ/ivT9JAgAaGPrUoKqKXqvademh6CPQSLxHZth0Kj/PeO8yDQXe0qiuQZf/4eM3XwRev5qcIgCVaWGaEdWHSV3qRSlFoZBzUY3EMDe6NKCAkaXq+2mRXs6wHZcafOPWAe33U9Nz1IJVPSOmB9+0dWVAhrTx05U+Eklau40ydlKhyWusgcUgWw2nnK2fAYTrNScSzWaXDHlwYQd3htcLNVleRw8hcJ1PV/0k2zij3K6161gmnwyPwTg+miaKO0pQgc90Lid19FtVdvvbUqpOsbQDF/V8IgT8CHnXZNd9GE2CpVYEaRAZpUNjSIQQBL4a7836iw0c+w0pycJqNG+OvmiVN5Xvi5RhNYbzp1TxM98W4Kr9oI87FHTOThuUVwcWoP3RFYkVlLa6BZ0fCToeyH7fOO5BQvgM+sEOxIkM7puLG4Y75ZRqtu3YXyWKY0curM/fbbVDtlOJoE3ugzWFUIDq+Xy+Wn8WpmMi4q2yL69nlmxV8h82SAUKu9OqDID46gH3rsH8Hd4VrTTEh6iJTscK8M3tvoLSwW7NZCT7Og7y35pP8lJ7RGh5KshGFbc2FpGhfCFYod78w+9GxlZnIhE3jX185k0pLDMgGjHE0ymr1/4JhPbpN9HILRsv2IN0HzD5tuZjs=) 58 | 59 | <<< @/components/checkbox/checkbox-without-input.vue 60 | 61 | ## Vue Patterns 62 | 63 | To implement `v-model` on a component, we need to define a prop called `modelValue` and an event called `update:modelValue`. This pair is used automatically when the parent component uses the `v-model` directive. The current value will be passed in using the prop. When the checkbox component needs to change that value it will emit the event, along with the new value. 64 | 65 | As the first example is using an `` element, it can also use `v-model` in its template. It can't bind directly to the prop, as reassigning the prop would not update the parent component. Instead, it uses a `computed` with a `get`/`set` pair to proxy the prop's value through to the `v-model`. The `get` just passes on the prop, while the `set` emits the relevant event. 66 | 67 | It could also be implemented without using `v-model` on the ``. Instead, we could use something like this: 68 | 69 | ```vue-html 70 | 75 | ``` 76 | 77 | This is conceptually very similar to how it is implemented in the second example. As the second example isn't using an ``, it uses a CSS class to change the appearance of the checkbox to include a checkmark. The `click` event is serving a similar role to the `change` event in the code above. 78 | 79 | One downside to using an `` is that the displayed value changes automatically when it is clicked. This might sound like a good thing, but it isn't, as the change is happening outside of Vue's control. In general, we want rendering updates to be controlled by Vue and a component should faithfully render the value of its props. When the checkbox is clicked, the parent component should have the option to ignore the emitted event and leave the checkbox unchanged. While this isn't usually something that you'd need, it can be invaluable in identifying bugs in the parent component. If the parent isn't correctly updating the 'source of truth' for the value, having a checkbox that correctly renders that value makes it much easier to find the problem quickly. A checkbox that smothers the problem and updates itself on every click is actually really unhelpful during debugging. 80 | 81 | The problem is similar to a common mistake made by beginners, where a component copies a prop into internal state and then uses a watcher to keep the two in sync. The idea is that the state can be used to bind an internal `v-model`, working around the restriction on mutating props. While there may be rare edge cases where this approach is justified, it should be seen as a last resort. Again, the heart of the problem is that the parent component may choose to ignore the emitted event, leading to the watcher not being triggered and the internal state being out of sync with the prop. 82 | 83 | ## Missing Functionality 84 | 85 | These checkbox components only support binding to a `true`/`false` value via `v-model`. A real checkbox component would typically be a lot more flexible. Vue's built-in support for native HTML checkboxes allows the `true-value` and `false-value` to be configured, as well as supporting `Array` and `Set` values that can be used to bind multiple checkboxes to the same collection. 86 | 87 | As the first example uses a native ``, it gets a few accessibility features for free. It has basic keyboard support and should be understood by assistive technologies, as well as any other tools that rely on parsing semantic information from the HTML. The second example doesn't have any of that. 88 | 89 | A real checkbox component would likely have support for a `