Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Creating accessible modals and autocompletes

606 views

Published on

In this two-part presentation, Russ will guide us on a deep dive into how to create accessible modals and accessible autocomplete search functions. Along the way, we will look at the problem for different types of users as well as explore how ARIA can be used to improve these experiences. There will be blood, sweat and tears (Russ' words!) but hopefully a happy outcome for all.

Presentation for the Sydney Web Accessibility & Inclusive Design - 30 August 2019

Published in: Education
  • Be the first to comment

Creating accessible modals and autocompletes

  1. 1. Part 1: Accessible
 modals
  2. 2. Non-native widgets
  3. 3. Before we look at modals, a quick overview of the term “non-native widgets”.
  4. 4. Let’s start with the term “widgets”.
  5. 5. The W3C defines “Widgets” as: “user-interface objects that enable users to perform a function”.
  6. 6. How about a simpler explanation?
  7. 7. Widgets can be simple objects such as a check box.
  8. 8. They can also be complex objects such as drop-downs, date pickers or even grids.
  9. 9. Basically, any self-contained UI component.
  10. 10. What about the term “non- native”?
  11. 11. “Non-native” means that the widget has not been built using native HTML elements. (Elements that were designed for that purpose).
  12. 12. Let’s use an example of a simple drop-down menu.
  13. 13. Choose an option Favourite fruit Label associated with dropdown
  14. 14. Choose an option Favourite fruit Dropdown
  15. 15. A native method would be to use the <select> and <option> elements.
  16. 16. <label for="fruit">Favourite fruit</label> <select id="fruit"> <option>Choose an option</option> <option>Apple</option> <option>Apricot</option> <option>Avocado</option> </select>
  17. 17. The <select> and <option> elements have semantic meaning.
  18. 18. The W3C defines the <select> element as an “option selector”.
  19. 19. The <option> element is defined as a “selectable choice”.
  20. 20. These elements are understood by all of the different accessibility APIs associated with browsers.
  21. 21. For example, the <select> element has a role of “combobox”.
  22. 22. There are also a range of 
 pre-defined keystrokes that 
 can be used to interact with these elements.
  23. 23. However, <option> elements are impossible to style with CSS.
  24. 24. So, developers often choose non-native alternatives 
 for drop-downs, to provide them with more control.
  25. 25. One common method is to use the <button> and <ul> elements.
  26. 26. Favourite fruit Button
  27. 27. Favourite fruit Apple Apricot Avocado List
  28. 28. <button>Favourite fruit</button> <ul> <li>Apple</li> <li>Apricot</li> <li>Avocado</li> <li>Banana</li> </ul>
  29. 29. This is an example of a non- native widget, as elements are being used in a different way to their defined purpose.
  30. 30. How is “non- native”relevant here?
  31. 31. Well, this “non-native” concept is relevant for modals and autocompletes.
  32. 32. While it is possible to build modals and autocompletes using native HTML elements, most developers don’t.
  33. 33. So, these widgets often end up being non-native.
  34. 34. For example, there is a <dialog> element designed for modal dialogs. https://www.w3.org/TR/html52/interactive- elements.html#the-dialog-element
  35. 35. <dialog open> <p>Hello world</p> </dialog>
  36. 36. However, this element is reasonably new and has limited support across devices and Assistive Technologies.
  37. 37. There is also a <datalist> element designed for simple autosuggests. https://www.w3.org/TR/html52/sec-forms.html#the- datalist-element
  38. 38. <label for="choice">Choose a flavor:</label> <input list="flavors" id="choice"> <datalist id="flavors"> <option value="Chocolate"> <option value="Coconut"> <option value="Mint"> <option value="Strawberry"> <option value="Vanilla"> </datalist>
  39. 39. But this element has limited functionality and doesn’t suit the needs of most autosuggest widgets.
  40. 40. So, it’s often easier and more practical for developers to choose the dark side.
  41. 41. On top of this, a lot of developers these days don’t know about basic semantic markup.
  42. 42. So, they don’t even choose the dark side. They start there!
  43. 43. But is non-native evil?
  44. 44. In many cases, non-native widgets need a lot of work in order to be accessible.
  45. 45. 1. Non-native widgets must be given a role so that their function is understood.
  46. 46. 2. Users must be notified of dynamic changes to the widget (i.e. has it changed state?).
  47. 47. 3. In some cases, additional instructions may be needed so that users can learn how to use the widget.
  48. 48. 4. All functionality must be accessible via keystrokes only.
  49. 49. 5. Keystrokes should be intuitive, and follow keystroke methodologies used on native elements.
  50. 50. So… that is the end of my
 “non-native widgets” rant…
  51. 51. Let’s dive into accessible modals.
  52. 52. What is a modal?
  53. 53. A modal is a function that requires the user to perform some sort of action before proceeding.
  54. 54. Generally, modals are separate “windows” that sit over the top of the parent page.
  55. 55. Account name: Choose an account Account number: Submit xGreyed out background (in some cases)
  56. 56. Account name: Choose an account Account number: Submit xModal window
  57. 57. Account name: Choose an account Account number: Submit xClose button
  58. 58. Account name: Choose an account Account number: Submit x Main heading inside modal
  59. 59. Account name: Choose an account Account number: Submit x 2 x Inputs and their labels
  60. 60. Account name: Choose an account Account number: Submit x Submit button
  61. 61. Common problems with modals
  62. 62. The following are a short list of common modal accessibility problems.
  63. 63. In reality, we could spend the rest of this presentation just listing known problems… but let’s not.
  64. 64. 1. The modal is not accessible via keyboard-only.
  65. 65. This means that keyboard-only users may not be able to access, interact with or close the modal.
  66. 66. 2. Keyboard-only users are able to use TAB outside of the modal window while the modal is still active.
  67. 67. This means that keyboard-only users can get trapped outside the modal.
  68. 68. And screen reader users assume that the modal has been dismissed - even though it still sits on top of the page.
  69. 69. 3. The modal is available to screen readers even when it is not triggered.
  70. 70. This means that the modal information is announced to screen reader users at inappropriate times.
  71. 71. 4. When the modal is triggered, screen reader users are not informed of its role or purpose.
  72. 72. This means that screen reader users may have no idea where they are or what task they are supposed to perform.
  73. 73. 5. Focus is often sent to the top of the parent page after a modal window has been closed.
  74. 74. This means that keyboard-only users have to navigate back to the relevant area themselves.
  75. 75. So, how can we build an accessible modal?
  76. 76. 1. Set initial focus
  77. 77. When a modal is triggered, some developers set the initial focus on the first focusable element inside the modal.
  78. 78. For example, focus may be placed on the first form control.
  79. 79. Account name: Choose an account Account number: Submit x Focus on first form control
  80. 80. This means that Assistive Technology users may not be able to determine the overall purpose of the modal.
  81. 81. Instead, I prefer to set focus on the modal window itself. At least for modals where the purpose is not immediately clear.
  82. 82. Account name: Choose an account Account number: Submit xFocus on modal window
  83. 83. Then additional context can be provided via a label. (Described in step 3)
  84. 84. 2. Add a role
  85. 85. A role should be provided on the parent container.
  86. 86. <div role="dialog"> … </div>
  87. 87. This role is announced to Assistive Technology users, informing them about the widget’s function.
  88. 88. Avoid using a role with a value of alertdialog unless the modal has a critical purpose.
  89. 89. <div role="alertdialog"> … </div>
  90. 90. The alertdialog value should only be used to notify the user of urgent information that demands the user's immediate attention.
  91. 91. 3. Add a label
  92. 92. A label should be used to inform Assistive Technology users of the modal window’s purpose.
  93. 93. One way to achieve this is to point an aria-labelledby attribute at the main heading inside the modal.
  94. 94. <div role="dialog" aria-labelledby="modal-label"> <h1 id="modal-label">Choose account</h1> </div>
  95. 95. 4. Add a description (optional)
  96. 96. A description can also be added to provide a more detailed description of the modal’s purpose.
  97. 97. This can be achieved by pointing an aria-describedby attribute at the first paragraph of text inside the modal.
  98. 98. <div role="dialog" aria-labelledby="modal-label" aria-describedby="modal-description"> <h1 id="modal-label">Choose account</h1> <p id="modal-description">Description here</p> </div>
  99. 99. If there is no headings or paragraphs within the modal, hidden information can be used instead.
  100. 100. 5. Defining keystrokes
  101. 101. When inside the modal…
  102. 102. Users should be able to use TAB and SHIFT + TAB to move forwards and backwards through focusable elements.
  103. 103. Users should not be able to use TAB to move focus outside the modal.
  104. 104. Focus should cycle through focusable elements within the modal only.
  105. 105. Users should be able to use standard keystrokes on any form elements inside the modal. (Radios, checkboxes, selects and buttons).
  106. 106. Users should be able to use ESC to close the modal and return to the parent page.
  107. 107. 6. Add meaning (optional)
  108. 108. For some important actions, screen reader users may need to be given additional information to let them know what will happen.
  109. 109. Account name: Choose an account Account number: Submit x Submit button
  110. 110. “Submit. Your data will be added to the bank balance table”
  111. 111. <button aria-label="Submit. Your data will be added to the bank balance table"> Submit </button>
  112. 112. Account name: Choose an account Account number: Submit xClose button
  113. 113. “Close and return to bank information”
  114. 114. <button aria-label="Close and return to bank information"> X </button>
  115. 115. 7. Send focus
  116. 116. When the modal window has been closed (via, ESC, close or submit function):
  117. 117. Focus should be set on the relevant component of the parent page.
  118. 118. The user should not be sent to the top of the parent page unless there is a good reason!
  119. 119. For simpler modals, especially on close, focus can be sent back to the trigger function.
  120. 120. If the modal causes information to be dynamically added to the parent page below, focus should be sent to this newly added information.
  121. 121. Ideally, this newly added information should be announced to Assistive Technologies.
  122. 122. “$1200.34 added to your bank balance”
  123. 123. 8. Test
  124. 124. Regardless of whether you use some or all of these suggestions, you should always conduct your own tests.
  125. 125. And remember:
 context is everything.
  126. 126. Online examples?
  127. 127. The Incredible Accessible Modal Window, Version 4 http://gdkraus.github.io/accessible-modal-dialog/
  128. 128. Bootstrap V4 modal https://getbootstrap.com/docs/4.3/components/ modal/
  129. 129. Van11y modal https://van11y.net/downloads/modal/demo/ index.html
  130. 130. Part 2: Accessible
 Autocompletes
  131. 131. What are they?
  132. 132. “Autocomplete” is a software function that provides relevant suggestions based on input by the user.
  133. 133. For this presentation, we’re going to focus on an example autocomplete associated with searching for towns in Australia.
  134. 134. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing autosuggest search component Label
  135. 135. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing autosuggest search component Input
  136. 136. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing autosuggest search component Clear mechanism
  137. 137. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing autosuggest search component Submit button
  138. 138. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing autosuggest search component Drop-down suggestions list
  139. 139. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing autosuggest search component Scroll bar (if needed)
  140. 140. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing autosuggest search component Results area below
  141. 141. How do we make an autocomplete widget accessible?
  142. 142. Some of this is going to sound awfully familiar…
  143. 143. Keyboard only?
  144. 144. Keyboard-only users should be able to perform any of the following actions…
  145. 145. 1. Use the TAB keystroke to move focus into the search input field from a previous element with focus.
  146. 146. Search towns in Australia Diagram showing input in focus TAB
  147. 147. 2. Use the TAB keystroke to move focus from the search input to the “clear” button.
  148. 148. Search towns in Australia Adaminaby, NSW Diagram showing clear button in focus TAB
  149. 149. 3. Use the ENTER keystroke to trigger the “clear” button.
  150. 150. Search towns in Australia Adaminaby, NSW Diagram showing selected clear button ENTER
  151. 151. Note: When the “clear” button has been triggered, the search input field should be cleared and focus should shift to this field again.
  152. 152. Search towns in Australia Diagram showing focus move for clear button back to search input
  153. 153. 4. Use the TAB keystroke to move focus from the clear button to the submit button.
  154. 154. Adaminaby, NSW Search towns in Australia Diagram showing focus move form the clear button to the submit button TAB
  155. 155. 5. Use the ENTER keystroke to trigger the submit button.
  156. 156. Search towns in Australia Adaminaby, NSW Diagram showing selected submit button ENTER
  157. 157. Note: When the submit button has been triggered, focus should shift to the first search result below the autocomplete search widget.
  158. 158. Search towns in Australia Bell, NSW Bell is a small rural and residential village in the Blue Mountains region of New South Wales. Bells Beach, VIC Bells Beach is a coastal locality of Victoria, Australia iand a renowned surf beach. Bell Diagram showing focus move from submit button to first search result
  159. 159. 6. Use the DOWN ARROW keystroke to move focus from the search input field to the first item in list of autocomplete suggestions.
  160. 160. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing focus move from input to first suggestion DOWN ARROW
  161. 161. 7. Use the UP ARROW and DOWN ARROW keystrokes to navigate backwards and forwards through suggestions.
  162. 162. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing second selection in focus and arrows to indicate focus can move backwards or forwards DOWN ARROW UP ARROW
  163. 163. 8: Users should not be able to DOWN ARROW past the last suggestion option.
  164. 164. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing last selection in focus and red cross to indicate focus cannot go forward DOWN ARROW
  165. 165. 9. Some developers allow DOWN ARROW keystrokes to loop from the last suggestion directly back to the initial input box.
  166. 166. However, I have found that some users find this confusing. They may not be aware that they have returning to the input field.
  167. 167. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing last selection in focus and red cross to indicate focus cannot jump to search input DOWN ARROW
  168. 168. I prefer to follow the default <select> behaviour where focus loops through all options only. From last back to first etc.
  169. 169. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing last selection in focus and focus jumping to the first option DOWN ARROW
  170. 170. 10. Use the ENTER keystrokes to select an autocomplete suggestion.
  171. 171. Search towns in Australia ar Arltunga, NT Armadale, WA Armidale, NSW Arno Bay, SA Diagram showing selected suggest option ENTER
  172. 172. Note: When the ENTER keystroke has been triggered, focus should shift back to the search input field.
  173. 173. Search towns in Australia Armadale, WA Diagram showing focus move from selected suggestion to search input field
  174. 174. 11. Use the ESC keystroke to close the suggestion list and return focus to the initial input (i.e. if none of the suggestions are relevant).
  175. 175. Search towns in Australia ar Diagram showing focus returning to input ESC
  176. 176. Accessible Markup?
  177. 177. There are a wide range of different ways to mark up an accessible auto-suggest widget. Here are some suggestions.
  178. 178. A quick overview
  179. 179. The <label> and <input> elements are the core of the search button.
  180. 180. <label></label> <input>
  181. 181. In our example, one <button> element will be used to “clear” user input.
  182. 182. <label></label> <input> <button></button>
  183. 183. And a second <button> element will be used to submit the form.
  184. 184. <label></label> <input> <button></button> <button></button>
  185. 185. The <ul> allows us to display the list of suggestions when appropriate.
  186. 186. <label></label> <input> <button></button> <button></button> <ul> <li></li> <li></li> <li></li> </ul>
  187. 187. The <div> element allows us to provide hidden instructions for screen reader users.
  188. 188. <label></label> <input> <button></button> <button></button> <ul> <li></li> <li></li> <li></li> </ul> <div></div>
  189. 189. FOR and ID attributes
  190. 190. In order to explicitly associate the <label> element with the <input> element, we should use for and id attributes.
  191. 191. <label for="search">Search towns in Australia</label> <input id="search" >
  192. 192. TYPE attribute
  193. 193. The <input> element’s type attribute could be set to a value of "text" and not "search".
  194. 194. <label for="search">Search towns in Australia</label> <input id="search" type="text" >
  195. 195. Some browsers, like Chrome and Safari will display an <input> type of "search" with a native “clear” button at the right side of the input.
  196. 196. Other browses like Firefox, do not show this function at all.
  197. 197. Chrome Firefox Safari Diagram showing Chrome and Safari’s clear button. Firefox has no clear button.
  198. 198. More importantly, this native “clear” button often cannot be accessed via the TAB keystroke, so it is inaccessible for many Assistive Technology users.
  199. 199. So, it is better to use a separate <button> element for clearing the field.
  200. 200. ARIA-DESCRIBEDBY
  201. 201. The aria-describedby attribute can be used to provide basic instructions on the use of the widget for Assistive Technologies.
  202. 202. This points to a matching ID value inside the hidden <div> element below.
  203. 203. <label for="search">Search towns in Australia</label> <input id="search" type="text" aria-describedby="instructions" > <div id="instructions" aria-live="assertive" style="display: none;"> When autocomplete options are available, use up and down arrows to review and enter to select. </div>
  204. 204. ARIA-OWNS
  205. 205. The aria-owns attribute defines a “parent/child contextual relationship to assistive technologies that is otherwise impossible to infer from the DOM”.
  206. 206. In other words, we can define the <input> element as the parent, and the <ul> element as the child element.
  207. 207. <label for="search">Search towns in Australia</label> <input id="search" type="text" aria-describedby="instructions" aria-owns="results" > <ul id="results"> ... </ul>
  208. 208. ARIA-EXPANDED
  209. 209. The aria-expanded attribute allows us to inform assistive technologies when the autocomplete dropdown
 is present (expanded).
  210. 210. The aria-expanded attribute should be initially set to "false".
  211. 211. <label for="search">Search towns in Australia</label> <input id="search" type="text" aria-describedby="instructions" aria-owns="results" aria-expanded="false" >
  212. 212. This value needs to dynamically change to "true" as soon as the autocomplete suggestions are present.
  213. 213. <label for="search">Search towns in Australia</label> <input id="search" type="text" aria-describedby="instructions" aria-owns="results" aria-expanded="true" >
  214. 214. Buttons
  215. 215. Directly after the input, we need two <button> elements.
  216. 216. The first <button> should be a type of "button" and allow users to clear the input.
  217. 217. <button type="button" aria-label="Clear"></button> <button type="submit" aria-label="Search"></button>
  218. 218. The second <button> should be a type of "submit" and allow users to submit the form.
  219. 219. <button type="button" aria-label="Clear"></button> <button type="submit" aria-label="Search"></button>
  220. 220. You may want to use icons instead of text for one or both of the buttons. In this case we are using “clear” and “search” icons.
  221. 221. Search towns in Australia Diagram showing clear and search icons
  222. 222. However, if you user icons instead of text, you will need to provide additional context for Assistive Technologies.
  223. 223. In this case, we can use aria- label attributes to provide hidden labels for both buttons.
  224. 224. <button type="button" aria-label="Clear"></button> <button type="submit" aria-label="Search"></button>
  225. 225. Unordered list
  226. 226. After the two button elements, we need to add the <ul> element which will be used to display the autocomplete suggestions.
  227. 227. <ul id="results" > </ul>
  228. 228. The role attribute can be set with "listbox".
  229. 229. This informs assistive technologies that the element is a widget that allows the user to select one or more items from a list of choices.
  230. 230. <ul id="results" role="listbox" > </ul>
  231. 231. To make sure the element cannot be brought into focus before it is triggered, we can set the tabindex attribute to "-1".
  232. 232. <ul id="results" role="listbox" tabindex="-1" > </ul>
  233. 233. This value needs to dynamically change to "0" as soon as the autocomplete suggestions are present.
  234. 234. <ul id="results" role="listbox" tabindex="0" > </ul>
  235. 235. To make sure the element is initially hidden we can set the style attribute to "display:none".
  236. 236. <ul id="results" role="listbox" tabindex="-1" style="display: none;" > </ul>
  237. 237. This value needs to dynamically change to something like "display:block" as soon as the autocomplete options are triggered.
  238. 238. <ul id="results" role="listbox" tabindex=“-1" style="display: block;" > </ul>
  239. 239. List items
  240. 240. Each of the <li> elements can be given a role attribute with a value of "option".
  241. 241. This informs assistive technologies that they are selectable items in a select list.
  242. 242. <ul id="results" role="listbox" tabindex="-1" style="display: none;" > <li role="option">apple</li> <li role="option">banana</li> <li role="option">pear</li> </ul>
  243. 243. Each of the <li> elements needs to have an aria- selected attribute initially set to "false".
  244. 244. <ul id="results" role="listbox" tabindex="-1" style="display: none;" > <li role="option" aria-selected="false">apple</li> <li role="option" aria-selected="false">banana</li> <li role="option" aria-selected="false">pear</li> </ul>
  245. 245. This value needs to dynamically change to "true" if the individual option is selected.
  246. 246. <ul id="results" role="listbox" tabindex="-1" style="display: none;" > <li role="option" aria-selected="true">apple</li> <li role="option" aria-selected="false">banana</li> <li role="option" aria-selected="false">pear</li> </ul>
  247. 247. The hidden DIV
  248. 248. After the <ul> element, we have the <div> element, which has the instructions for assistive technologies.
  249. 249. <div> When autocomplete options are available, use up and down arrows to review and enter to select. </div>
  250. 250. As mentioned before, the ID value allows us to point the <input> element to this <div> element via the aria- describedby attribute.
  251. 251. <div id="instructions" > When autocomplete options are available, use up and down arrows to review and enter to select. </div>
  252. 252. The <div> element needs to be visually hidden, but still available to screen readers.
  253. 253. This can be achieved by setting it “off-left” using CSS. So, we can give it a pretend “off-left” class here.
  254. 254. <div id="instructions" class="off-left" > When autocomplete options are available, use up and down arrows to review and enter to select. </div>
  255. 255. The aria-live attribute is set to "assertive". This informs assistive technologies as soon as anything inside this element is dynamically changed.
  256. 256. <div id="instructions" class="off-left" aria-live="assertive" > When autocomplete options are available, use up and down arrows to review and enter to select. </div>
  257. 257. We need this because the instructions will dynamically change as soon as the autocomplete options are triggered.
  258. 258. <div id="instructions" class="off-left" aria-live="assertive" > When autocomplete results are available use up and down arrows to review and enter to select. </div>
  259. 259. <div id="instructions" class="off-left" aria-live="assertive" > 6 options available. Use up and down arrows to review and enter to select. </div>
  260. 260. The instructions should also immediately change as users type if the number of suggestions changes.
  261. 261. <div id="instructions" class="off-left" aria-live="assertive" > 3 options available. Use up and down arrows to review and enter to select. </div>
  262. 262. The ideal method?
  263. 263. As mentioned before, this is just one method that could be used.
  264. 264. Before deciding, make sure you check out different methods and test them - with real users, and across different assistive technologies.
  265. 265. Good examples?
  266. 266. Haltersweb Accessible Autocomplete https://haltersweb.github.io/Accessibility/ autocomplete.html
  267. 267. Vision Australia Autocomplete http://www.visionaustralia.org/digital-access-autocomplete
  268. 268. jQuery accessible autocomplete list (with some ARIA) https://a11y.nicolas-hoffmann.net/autocomplete-list/
  269. 269. Alphagov Accessible Autocomplete examples https://alphagov.github.io/accessible-autocomplete/examples/
  270. 270. OpenAjax Combobox with aria- autocomplete="inline" http://oaa-accessibility.org/examplep/combobox2/
  271. 271. Final note
  272. 272. Anyone who is designing or building web apps has to deal with non-native widgets.
  273. 273. Modals, autocompletes, dropdowns, date pickers, sortable tables… the list is endless.
  274. 274. The key message here is that non-native widgets can present a range of barriers.
  275. 275. However, these barriers can always be resolved. It just takes a bit more time and effort.
  276. 276. Russ Weakley Max Design Site: maxdesign.com.au Twitter: twitter.com/russmaxdesign Slideshare: slideshare.net/maxdesign Linkedin: linkedin.com/in/russweakley

×