Vision Direct’s Design System and Custom Features across Seven Multi-Markets
The work sat inside a large, shared e-commerce platform mid-migration to Luxottica’s custom CMS. The brief was a tightrope: reduce friction in a high-frequency medical purchase — contact lenses — and lift conversion without breaking a revenue-critical system. So everything shipped incrementally, as component restructuring within the existing design system.
I reworked the high-impact flows — payments, subscriptions, address management, autosave, prescription entry and bundles — then restructured the cross-brand colour system and localised each journey for the UK, Italy, Spain, France, Ireland and the Netherlands. Fiscal codes, regional payment vendors, currency formatting and shipping rules all changed market to market.
OverviewOverview
Vision Direct is part of the EssilorLuxottica group and operates within a shared, large-scale e-commerce platform used across multiple optical brands.
The project was not a full redesign, but a controlled optimisation and migration effort. My role was to support component restructuring within the existing design system and improve high-risk, high-impact flows such as prescription input, payments, saved addresses, bundles, account updates, and password resets—without disrupting conversion.
The rollout was phased, starting with Italy, followed by the UK, and then extended across multiple Vision Direct markets including Spain, France, Ireland, and the Netherlands.
I worked in a fast-paced, cross-functional team alongside a Lead Designer, Technical Director, Project Manager, and local copywriters for each market.
02Focus Areas
- Optimisation & migration to the new Luxottica custom CMS
- Expanding (current) UK system and scaling it to 7 markets
- Features: Subscription, NIN Insurance flow, Improving CHeckout(One button checkout, Address management, Apple Pay), Autosave account, Guest Checkout, Express Checkout, Fiscal Code / Codice Fiscale (IT), Limiting Shipping to Middle East, Gifts (Bundles), NIF Code (ES), Credit Mutuelle (FR), Improved Voucher Codes
03Challenges + why it matters?
How do we strive to retail 10+ yrs loyal customers while driving new features?
Reduce friction in a high-frequency medical purchase (contact lenses) and improve conversion without breaking an existing, revenue-critical system?
Technical Constrains of a large platform, small incremental changes
Selected Features

04Subscription Experience – Vision Direct
Worked on the initial design for Vision Direct’s subscription flows, creating the first structured experience for recurring orders. Key contributions:
- Mapped the core subscription journey including onboarding, account management, and one-time purchases.
- Designed interactions for pausing and reactivating subscriptions, as well as adjusting delivery dates and frequency.
- Address & Payment Handling: Conceptualized address edits and payment flows to support recurring orders.
- Initial Design System Integration: Ensured components could be migrated into Luxottica’s broader design system in the future.
This work laid the foundation for Vision Direct’s subscription experience, providing a clear structure for iteration and refinement.

05Collection Point (ES)
Option to ship to a collection point as a premiere inhouse feature for the Spanish market. The service uses Myflexbox API and followed the documentation standards for collection and the edge cases around the using this product.

06Reorder Again – Vision Direct
Designed the first “Reorder Again” flow, enabling users to quickly repeat past purchases. Built core interactions for selecting items, adding them to the basket, and handling saved addresses and payments, establishing the foundation for repeat-order UX.

07Price Components
As we created the Homepage and PDP pages up to seven markets we run into an issue of how to display prices on product tiles. Even if we had the price euro sign 〈€〉, the display was slightly different in some eurozone countries. For example in Ireland, the euro sign comes before the price with no space (similar to how pounds are displayed in the UK), the dutch (and BENL) version is the same but has a space between while the rest of eurozone has the sign.
Manually copying and pasting for each instance would have been intensive and would modify too much the components that would create issues in the future, so the realistic solution would be create variants of the product label section which is was already a component.

Updating the component over 477+ instances required a bit of planning, naming for the price was kept the same to match the current price and the extra currency symbol were deleted using the search and replace feature in Figma.

The result was a set of tiles that would easily interchange maintaining the edited price, while switching the currency symbol in the appropriate place depending of the market.


08Autosave
As we added features we stacked all of them into a page and split them into sections rather than a multitude of separate pages. However, one inconvenience was we had to create a logic of how these sections would be individually saved.
So we talked with the tech team and the best solution was having autosave on all input fields without the need to hit a save button every time. This required a thorough evaluation for all input fields and thinking over where signposting is needed.

09Managing Address Book
Designed the first address management flows for subscriptions, letting users add, edit, delete, and select default addresses. Solving the issue of having a multiple addresses and how the default address is setup.
10Payments
The payment system consists in one main payment (mostly card) method and the secondary ones. In previous designs were scattered over different market specifics, for example in the Netherlands the primary payment method was iDeal, an e-commerce payment system started in 2005-2006 by a consortium of NL banks. Same in France and Belgium with Bankcontact, these services lost market share as new Genz have more trust in giving credit card details as they manage their accounts via banking apps.
So the push was to push to the customers to use cards as much as possible, prioritising saving and gathering credit/debit card data rather than using third-party vendors. Also making sure that new financial services that offer post-purchase payments (buy now, pay later), like Klarna, Affirm & Sizzle.
The tasks were:
- Set a universal system that would prioritise card payment first but keep the legacy vendors like IDeal and Bankcontact options open.
- Create a seamless management system where a user can easily save and add additional cards on Vision Direct
- Integrate third-party vendors like Klarna and Credit Mutuel
First-time checkout
When a first-time user gets to the checkout, or we don’t have any stored card in the system, we show the new card tile by default open, aside from the other 3rd party payment methods.
Recurring Visit
On a recurring visit, we want the least amount of friction or decision-making possible so we worked out the easiest flow possible considering the legal and technical requirements.
When a recurring customer is at checkout the card from the previous session is stored and shown as a preview (last 4 digits & payment network logo), once they accept the T&C’s the checkout is complete.

11Multiple payment methods
As we tackled multiple markets one of the key differentiators was payment types as every country favoured a specific payment method.

12The quantity field
We had a task to unify the product quantity dropdowns, which were initially separate and were able to order a different quantity of lenses for each eye. With this change, we sought to review the experience and see if there is any room for improvement as a matrix of dropdown input fields didn’t seem the best practice.
We went on the common variation that includes an input field with [-] and [+] buttons on the sides. The main reasons were:
- To break the pattern, multiple elements of the same kind may seem repetitive and feel like a chore (even for us designers). A break in the pattern makes the elements more distinguishable and pleasurable to complete or verify.
- The potential task for upsizing to more boxes is less of a hassle, reducing the number of clicks.
- The option to use the input field and keyboard, beneficial especially on products that can be ordered in large quantities.
To prototype all the edge cases we used variables in Figma as we wanted to showcase how the component would really behave within the edge cases that we had. The main advantage of using dynamic prototyping in Figma is the access to the Design System and the usage of if then that & boolean actions.
- With a minimum quantity of 1 and max of 8 boxes in the basket, we wanted to show how our buttons would switch from the ‘Enabled’ state to ‘Disable’ when the quantity reached the max and min levels.
- Price would be dynamically calculated each time a user tweaks the ‘packs’ or the ‘boxes’ fields.
- Be able to check and uncheck the ‘left’ and ‘Right’ checkboxes, which will turn on and off the respective input fields, retaining the selected value.
What we’ve learned: Broken Components
As many projects you enherit all sort of stuff and ever so often everyone has a certain style to work with components. Figma like any type of software that becomes so stuffy and a swiss army knifes has retain different ways that work to acompass everybody.
When instances of components don’t work usually there’s an error in the design system, so normally you should go on the pipeline and check all namings and the instance matrix.
Naming is a big issue, even though Figma tries to solve this from their backend, so when glitches happen, it is worth going though every element and making sure all have at least some differentiation.

13Upsell & Upgrade from basket
Designed the first in-basket upgrade and exchange flows, allowing users to add, upgrade, or exchange products before checkout. Defined core interactions for prompts, confirmations, and multi-product handling, laying the foundation for future upsell experiences.

14Persuade users to change their password
For some technical reasons, we could not migrate the Irish users to the new unified platform without a password change. This process would be a one-time event per account and the concept was to make it as seamless as possible without any blockers.
Initially, a page was set in place that was based on the ‘Wrong Password’ template, featuring an ‘Alert’ banner with just a copy change. However, in testing, this didn’t go well.
- The message error with the red background did not explain very well why the customer has to change the password
- Needed a CTA/link on the message alert to distinguish it from the standard error message
- Should it read “Reset Your Password” as in design, or “forgotten password” as in the spec?
- The landing page only has a field for email, yet the copy says reset your password when only the email is requested
The key was to differentiate the “Reset Password” from the “Forgot Password” flow, make signposting less alarming, and stick with the email.

A tailored page was created, without using the ‘red alert’ components like before pushing users to reset their password. We took patterns and ideas from situations like when a password was compromised, and worked with copywriters to make sure we were delivering the right tone of voice.
So a new ‘Reset your password’ page was created that appears once per user who never changed their password. The red alert bar was removed and copy was added explaining the situation, along with showing the email involved.
Main CTA: Reset your password redirects users to the ‘Forgot your password’ flow, while keeping an option to go back to login in case of multiple accounts or any edge case.


15Restructuring Colour Styles in the Design System
One of the tasks was to adhere to a unified design system called ‘Crossbrands’, which has a unified naming convention according to the front-end token system.
Even if those shades look fine, in reality this 4-year-old system was the result of two brand systems merged together. The main issues were:
- Many colours were identical or too similar, and over time different people assigned colours inconsistently
- The ‘Shade Gradient’ which was the cause of many issues had misleading naming
- Lack of control over the text & background colours
- More than 7 different styles for plain white
Find, replace and merge colours
A bit of a tedious process was to reverse search colours — large design systems in big crossbrand organisations often witness a lot of merging and rebrand attempts, so they rarely look like a ‘design system template’ from the Figma community.


This was necessary to plan strategically before making changes.

Even though Figma’s ecosystem offers a lot of plugins, the process isn’t entirely automated. A plugin can ‘see’ where a specific colour style is used and offers the option to ‘swap’, but that doesn’t respect hierarchy (Atoms first, then molecules, then organisms).
Running the plugin scripts directly would ‘modify’ all component colours at once, so a later change at the ‘Atoms’ level wouldn’t propagate to instances the script already touched. The tool works best as an assistant to navigate to the right place and make changes strategically.

The result was removing over 20 colour styles that were hardly used, defaulting to a solid set of colours aligned with the brand guidelines. This helped developers reduce errors and maintain the front-end, and helped designers assign colours seamlessly to new components.
- Scanning each colour and charting how many instances were used
- Creating a strategy for which colours are duplicates (especially whites) and which are close enough to merge — many shades were used solely by hover states, which wasn’t reason enough to keep a separate style
- Mocking up a new colour structure
- Merging styles by reassigning colours to each component manually
- Deleting colour styles, double-checking they weren’t wrongly assigned by scanning the visual design styles via remote styles
- Renaming remaining styles to match the new naming convention
- Adding new elements like opacity styles and new colours
- Splitting white and dark into two styles via manual reverse search

16Fiscal Code, Codice Fiscale & NIF (IT, ES)
These two markets have a fiscal code which is optional, but important enough to take into consideration as lots of people use it, especially when buying medically related items.
The main task was to find a way to relocate the fiscal code in the account and showcase it in the checkout flow for confirmation. The main pain point was understanding the tech team’s constraints while still providing the best user experience possible.
For presentation purposes we showcased the fiscal code for Italy first, in English. After the proof of concept was validated we translated everything into IT and ES, swapping the Fiscal Code (Codice Fiscale) for the NIF Code in the Spanish market.


We created a widget covering three routes:

Route A — The user adds a fiscal code from the Account and then gets to Checkout where the widget is prepopulated.

Route B — The user has no Fiscal Code in the account but adds one while in checkout.

A third category is users who had already entered a FC/NIF in the system and needed to be updated to reflect the changes.
Migration
For users who had already entered a fiscal code, we signposted the changes via info bubbles.

17Upsell a product from the basket
We worked out which products would have an upsell, mainly for business purposes based on data of products frequently bought together. Not all products have an upsell tied to them, and some require going to a PDP page to choose the right lens prescription — a case we solved separately.

If a customer has a product in the basket and wants to upsell a contact lens, instead of going to the PDP we show a quick-view modal where they can choose their CL values and add to basket effortlessly.

If a lens is already in the basket, we can copy the values of the current product straight across.


18Dealing with expired cards (Checkout + Account)
1. Set expiration checks
Implement a system to regularly check the expiration dates of stored cards, run as a scheduled task at regular intervals.
2. Notify users
Send notifications about a card’s expiration status well in advance, giving customers time to update their information.
3. Grace period
After a card expires, a grace period (typically 3–6 months) marks it as expired but not yet deleted, with periodic reminders sent to update payment information.
4. Automatic removal
Once the grace period ends: identify expired cards via a query, back up the data in compliance with data protection regulations, remove the expired cards, then send a final notification confirming the removal.
19Guest Checkout
In Germany, approximately 30–40% of e-commerce transactions are completed using guest checkout, while 60–70% are done by registered users — a higher registration rate than in some other European countries.
The higher registration rate is influenced by a few factors:
- Trust and Loyalty Programs — many German consumers value loyalty programme benefits and prefer accounts for personalised offers, discounts and quicker future checkouts
- Privacy Concerns — despite being cautious about data privacy, frequent online shoppers prefer having control over their account and data, leading them to register rather than check out as a guest
- Ease of Repeat Purchases — registered users can store their information for quicker future transactions

20Improved Promo Code
We improved promo codes from plain text into a more playful “Copy Code” journey where you can tap to copy the code, which is then automatically added to the checkout. We A/B tested a series of versions to understand how users would interact with the mental model — some read “Save Code” and applied automatically, others just copied the code to the pasteboard.
The most successful variant in testing was to “Save” the code, showing it in checkout with an option to “Apply” — customers liked seeing the price drop before committing, which nudged them toward completing checkout.

21Payment Form Redesign
One of my goals since joining the project was to investigate the payment forms, which I found quite clunky and tedious to use, and research better alternatives.
22Handling J&J products that are not available overseas
Vision Direct’s main strategy is to have designated websites for each main market, but there’s always the possibility of shipping to places without a dedicated website, like the UAE. The UK website allows shipping to the Emirates, but some products aren’t available there — J&J products like the Acuvue range, for instance.
We could simply dim the Buy button for a Dubai address, but the bigger concern was regular UK customers who work, live, or travel to the UAE occasionally. So we designed for two cases: first-time users on the UK site (a), and customers with a saved UK address who add an overseas one at checkout (b).
(a) A user without an account starts shopping on the UK website, which shows all products by default. When they enter a UAE address, unavailable items are flagged with available alternatives shown.

(b) A user who has previously checked out with only a UAE address saved sees the “Add to Cart” button disabled on unavailable products’ PDPs, with an alert banner explaining the situation.
(c) A mix of edge cases in between.

23Prescription Wizard
On mobile, the lack of space caused real issues with the prescription dropdowns — users reported the text was too small and the list too narrow. Eyewear prescriptions are a set of positive and negative values from 0 to 15, generally displayed in two columns, ‘Plus’ and ‘Minus’, for quick recognition and selection.
The ideal solution would have been a native app selector, but that’s only available in-app — the web selector risked becoming a tall list that splashes all over the screen.

In the short run, we redesigned the tables.

We discovered users usually have a set of 2–6 dropdowns they need to open, close and reopen again.


24Login field validation
Something as simple as a login screen can go unnoticed as just two input fields and a button pulled from the design system — but that’s not how it played out in practice.
While improving the ‘Reset Password’ journey — which uses the same email field as ‘Login’ — I noticed the input field validation wasn’t time-based as expected. It worked on outside click (including a button), which meant a user needed to click a button twice: once to validate the field and enable the button, then again to actually proceed or log in.
This stemmed from backend query performance, where a query was triggered by an outside click. So we agreed on a rule:
- For in-field validation, use it time-based (after 1s), skip buttons entirely, and show a snackbar to confirm the autosave
- Where buttons are required, keep them always active, with validation triggered only on click, surfacing Error states


