【MUI】StackでFlexboxのgapが使えるようになった(実験的機能)
はじめに
MUI v5.11.15 で Stack に useFlexGap prop が追加されました。
これまでは Stack の spacing を指定すると margin が設定されていましたが、useFlexGap を有効にすることで Flexbox の gap が設定されるようになります🎉環境
- @mui/material v5.11.16
デモで使用しているコードは下記の StackBlitz 上で試せるようにしています。もし、挙動を確認したい方がいればこちらも合わせてご確認ください。
前提
Stack 自体の説明は省きます🙏
Stack について知りたい方は下記を見ていただければと思います。
これまでの spacing の挙動
spacing を指定することで、子要素の間隔を設定できます。これまでは margin が使われていましたが、全ての子要素に margin をつけると無駄な余白ができてしまうので、下記のような一工夫が入っていました。(横並びの場合は marginLeft)
& > :not(style) + :not(style)': { marginTop: 16px }
簡単に説明すると、Stack 直下の子要素以外に対して margin を設定しています。つまり、要素の位置に依存する形で margin が適用されます。
margin による指定の問題点
order で子要素を並び替えるとデザインが崩れる
Stack 内の要素は order を指定することで任意の並びにできます。ただし、order で変更できるのは見た目上の並びで、ソース内の順序は変わりません。
その影響で上述した margin の適用時は order の指定が考慮されません。
試しに order を指定した時の挙動を確認してみましょう。
まずは order なしのパターンを見てみます。
import { Stack, Typography, TextField, Divider } from "@mui/material";
function App() {
return (
<Stack alignItems="center" spacing={4}>
<Typography variant="h5">これまで通り(margin)</Typography>
<Stack spacing={6}>
<Typography>テスト1</Typography>
<Typography>テスト2</Typography>
<Typography>テスト3</Typography>
</Stack>
</Stack>
);
}
export default App;
間隔が設定されているので良さそうです。
次に order を指定して並びを変えてみます。
import { Stack, Typography, TextField, Divider } from "@mui/material";
function App() {
return (
<Stack alignItems="center" spacing={4}>
<Typography variant="h5">これまで通り(margin)</Typography>
<Stack spacing={6}>
+ <Typography order={3}>テスト1</Typography>
+ <Typography order={1}>テスト2</Typography>
+ <Typography order={2}>テスト3</Typography>
</Stack>
</Stack>
);
}
export default App;
すると、テスト2
の上に不要な margin が設定されたのと、 テスト3・テスト1
の間隔が設定されなくなってしまいました。
orderによる要素の並び替え後
Devtools で確認してみると、テスト2
に対しても margin が適用されていることが分かります。このように margin だと order で子要素を並び替えた時にデザインが崩れてしまいます。
参考
Stack で横並びにして子要素の間隔を詰めるとずれてしまう
Stack の direction="row"
を指定して子要素を横並びにし、子要素として TextField を2つ並べてみます。また、TextField は margin="dense"
を指定することで詰めて並べることができます。
import { Stack, Typography, TextField, Divider } from "@mui/material";
function App() {
return (
<Stack alignItems="center" spacing={4}>
<Typography variant="h5">これまで通り(margin)</Typography>
<Stack spacing={6}>
<Typography order={3}>テスト1</Typography>
<Typography order={1}>テスト2</Typography>
<Typography order={2}>テスト3</Typography>
</Stack>
+ <Stack spacing={6} direction="row">
+ <TextField margin="dense" />
+ <TextField margin="dense" />
+ </Stack>
+ <Stack spacing={6} direction="row">
+ <TextField margin="dense" />
+ <TextField margin="dense" />
+ </Stack>
</Stack>
);
}
export default App;
すると、下記のようにテキストボックスが少しずれてしまいます。この現象についてはなぜ起きるのか把握できていません...
Stackで横並びにして上でTextFieldの余白を詰めた時
参考
Flexbox の gap を試してみる
margin で指定した時の問題点がわかったので、次は Flexbox の gap を使用して問題が解消されるか確認してみます。...と言いたいところですが、現時点では Flexbox の gap を適用する方法が2種類あるので、先に適用方法を簡単に確認しておきます。
Flexbox の gap を適用する方法
Stack ごとに Flexbox の gap を適用する
まずは、Stack に useFlexGap を指定する方法です。こちらは Stack ごとに変えられるので柔軟な設定が可能です。
<Stack spacing={6} useFlexGap>
<Typography order={3}>テスト1</Typography>
<Typography order={1}>テスト2</Typography>
<Typography order={2}>テスト3</Typography>
</Stack>
デフォルトを Flexbox の gap にする
これは useFlexGap に限らないですが、ThemeProvider を使うことで、アプリケーション全体の設定を変更できます。ここで MuiStack の useFlexGap を指定すると、デフォルトで Flexbox の gap が適用されます。
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
+ import { createTheme, ThemeProvider } from '@mui/material'
+ const customThema = createTheme({
+ components: {
+ MuiStack: {
+ defaultProps: {
+ useFlexGap: true,
+ }
+ }
+ }
+ })
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
+ <ThemeProvider theme={customThema}>
<App />
+ </ThemeProvider>
</React.StrictMode>,
)
個人的には、今後の手間を考えると ThemeProvider の useFlexGap を指定してデフォルトを変えるのが良さそうかなと考えています。理由は下記のコメントを見る限り、v6 から Flexbox の gap がデフォルトになりそうだからです。
Flexbox の gap がデフォルトになれば、useFlexGap の指定は不要になります。そうなると、useFlexGap を消したい!となりそうですが、Stack ごとに useFlexGap を指定していた場合は削除してまわる必要があります。一方、全体で適用している場合は1箇所修正すれば済むはずです。
useFlexGap を試してみる
margin による指定の問題点で使用したコードを複製して useFlexGap を適用してみます。
import { Stack, Typography, TextField, Divider } from "@mui/material";
function App() {
return (
<Stack alignItems="center" spacing={4}>
<Typography variant="h5">これまで通り(margin)</Typography>
<Stack spacing={6}>
<Typography order={3}>テスト1</Typography>
<Typography order={1}>テスト2</Typography>
<Typography order={2}>テスト3</Typography>
</Stack>
<Stack spacing={6} direction="row">
<TextField margin="dense" />
<TextField margin="dense" />
</Stack>
<Stack spacing={6} direction="row">
<TextField margin="dense" />
<TextField margin="dense" />
</Stack>
+ <Typography variant="h5">Flexbox の gap適用</Typography>
+ <Stack spacing={6} useFlexGap>
+ <Typography order={3}>テスト1</Typography>
+ <Typography order={1}>テスト2</Typography>
+ <Typography order={2}>テスト3</Typography>
+ </Stack>
+
+ <Stack spacing={6} direction="row" useFlexGap>
+ <TextField margin="dense" />
+ <TextField margin="dense" />
+ </Stack>
+ <Stack spacing={6} direction="row" useFlexGap>
+ <TextField margin="dense" />
+ <TextField margin="dense" />
+ </Stack>
</Stack>
);
}
export default App;
下記が実行した結果ですが、どちらの問題も解消されていますね🎉
useFlexGapを適用
Devtools で確認すると、確かに Flexbox に gap が適用されています。
おわりに
まだ MUI を使っているプロダクトでは試せてないので、そのうち試してみようかなと思っています💪(まずはバージョンを上げなくては...)
Discussion