Next.js & Fluent UI: Search Form
While working on a web application, built with Next.js and FluentUI, I noticed an issue with the form when trying to type into the input field on mobile devices.
During the typing process, the screen would zoom in, and leaving the input field would not return the screen to its initial position. This issue arises because the font size of the input element is smaller than 16px
. By default, FluentUI assigns a different font size to the input unless a custom theme is used.
Thus, input zooming happens in mobile views.
Fix input zoom issue
There are two ways to fix the issue: one is less recommended due to its potential accessibility implications, while the other is an acceptable and preferable approach.
Discouraged method: Disabling zooming
One common solution to the zooming issue is to disable the zooming functionality on a webpage. This can be accomplished by adding the following code snippet to the head of the webpage:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
While this technique may help to address the zooming problem, it also introduces accessibility issues.
Disabling zoom can hinder users who rely on zooming or scaling for readability, particularly those with vision-related disabilities or certain neurocognitive differences.
Therefore, even though this solution might appear helpful, it is not an ideal approach as it potentially inhibits a significant number of users from comfortably accessing your content.
Preferred method: Using media queries
An alternative, and more suitable, solution is to adjust the font size for the input component when viewed on a mobile device. If the base font size is smaller than 16px
, use media queries to increase it to 16px
for mobile views.
Here's a possible way to implement this:
.some-component {
/* Styles for desktop devices */
}
@media (hover: none) and (pointer: coarse) and (max-width: 767px) {
.some-component {
/* Increase the font size to 16px for mobile devices */
font-size: 16px;
}
}
In this example, .some-component
could be replaced with the class of your input component. The media query will then apply the 16px
font size to this input component for touch devices with screens smaller than 768px
.
Level 5 Media Queries hover
feature is used to query the capability of the user's primary input mechanism to hover over elements. The possible values are:
hover
: Indicates that the primary input mechanism can hover over elements with ease.none
: Indicates that the primary input mechanism cannot hover, or can only do so with great difficulty. This is typically the case with touchscreens.
This feature can be helpful to provide a better user experience, depending on whether the user's device can easily hover over page elements or not.
Level 5 Media Queries pointer
feature is used to query the accuracy of the pointing device. The possible values are:
fine
: Indicates a pointing device with a high level of accuracy. Examples include a mouse or a stylus.coarse
: Indicates a less accurate pointing device. This is typically a touchscreen or a games console controller.none
: Indicates that the device does not include any pointing device.
This feature helps to customize the user experience based on the capabilities of the user's device. For example, change the size of interactive elements on the page for devices with coarse pointers to make them easier to select.
Solution
Comparing the two methods, it is evident that while disabling zooming may solve the zoom issue at face value, it presents new accessibility concerns that may hinder the user experience.
On the other hand, using media queries to adjust the font size for mobile views is a much more acceptable solution as it preserves the zoom functionality for those who need it while also solving the issue at hand.
Example
As an example, build a component that displays a search form. The form includes input field to enter search keyword and button to submit form. This example uses acceptable way to fix zooming issue.
Search Form
After setting up FluentUI and Next.js project, create folder components
inside src
folder to keep custom components for the app.
Inside components
folder, create SearchForm.jsx
file:
"use client";
// Import the 'useState' hook from 'react' for managing local state.
import { useState } from "react";
// Import the necessary Fluent UI components and utilities.
// makeStyles: A utility function for generating custom CSS-in-JS styles.
// shorthands: An object of utility functions for CSS shorthand properties.
// tokens: An object of design token values, like spacing and colors.
// Field, Input, Button, Link: Fluent UI components to create the form and its elements.
import {
makeStyles,
shorthands,
tokens,
Field,
Input,
Button,
Link,
} from "@fluentui/react-components";
// Import the 'Search20Filled' icon from Fluent UI to use in the search input.
import { Search20Filled } from "@fluentui/react-icons";
/**
* Create a custom 'useStyles' hook to define the styles for the search form.
*
* The styles are applied using CSS-in-JS and make use of design tokens and
* shorthand utilities for common CSS properties.
*/
const useStyles = makeStyles({
form: {
...shorthands.gap(tokens.spacingVerticalXS),
display: "flex",
flexDirection: "row",
alignItems: "center",
width: "100%",
maxWidth: "30rem",
},
field: {
width: "100%",
},
input: {
"@media (hover: none) and (pointer: coarse) and (max-width: 767px)": {
fontSize: "16px",
},
},
button: {
marginBlockStart: "0.5rem",
},
result: {
maxWidth: "20rem",
textAlign: "center",
display: "flex",
flexDirection: "column",
...shorthands.gap(tokens.spacingVerticalXXS),
},
});
/**
* 'SearchForm' is a component that displays a search form.
* Upon submission, the form is replaced with a results section that displays
* the search keyword and some example text.
*
* @returns {React.Element} The SearchForm component that displays the form or the search results.
*/
export default function SearchForm() {
// Retrieve the styles from the 'useStyles' hook.
const styles = useStyles();
// State to store the keyword.
const [keyword, setKeyword] = useState("");
// State to handle if the form is submitted.
const [searched, setSearched] = useState(false);
/**
* Handles the form submission event.
*
* @param {Event} event - The form submission event.
*/
const handleSubmit = (event) => {
// Prevent default form submission.
event.preventDefault();
// Indicate that form was submitted.
setSearched(true);
};
// Render the form only if it hasn't been submitted.
if (!searched) {
return (
<form className={styles.form} onSubmit={handleSubmit}>
<Field
className={styles.field}
label="Search website"
hint="Enter keyword and press Search"
>
<Input
className={styles.input}
contentBefore={<Search20Filled />}
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
required
/>
</Field>
<Button className={styles.button} appearance="primary" type="submit">
Search
</Button>
</form>
);
}
// Render the search result section.
return (
<div className={styles.result}>
<p>
<strong>You searched for: {keyword}</strong>
</p>
<p>
This is example of a search results section, based on a keyword from
search form.
</p>
<p>
<Link href="https://www.tarascodes.com/fluentui">Learn more</Link>
</p>
</div>
);
}
Above, example of SearchForm
component, built with FluentUI and Next.js.
Code breakdown
The following is a walkthrough of how the problem was tackled in the code.
// Import the necessary Fluent UI components and utilities.
import { makeStyles } from "@fluentui/react-components";
The makeStyles
utility from Fluent UI is imported at the start. This utility enables us to create custom styles for the components using CSS-in-JS approach.
// Create a custom 'useStyles' hook to define the styles for the search form.
const useStyles = makeStyles({
input: {
"@media (hover: none) and (pointer: coarse) and (max-width: 767px)": {
fontSize: "16px",
},
},
});
Next, the useStyles
hook is declared. This is where the solution to the zoom issue is applied.
Inside the input
property, a media query is specified that targets mobile devices. On these devices, if the input's base font size is smaller than 16px
, the media query changes the font size to 16px
.
This adjustment prevents the zooming issue on mobile devices, ensuring that the input field doesn't trigger an automatic zoom in the mobile browser.
// Retrieve the styles from the 'useStyles' hook.
const styles = useStyles();
Here, the custom styles defined using the useStyles
hook are retrieved and stored in the styles
object, which can then be applied to the component's elements.
return (
<form className={styles.form} onSubmit={handleSubmit}>
<Field className={styles.field} label="Search website" hint="Enter keyword and press Search">
<Input className={styles.input} contentBefore={<Search20Filled />} value={keyword} onChange={(event) => setKeyword(event.target.value)} required />
</Field>
<Button className={styles.button} appearance="primary" type="submit">
Search
</Button>
</form>
);
Finally, the styles.input
which contains the mobile device specific font size adjustment is applied to the Input
component of the form. This ensures that whenever the form is displayed on a mobile device, the input field has a minimum font size of 16px
, thus addressing the zoom issue.