Button
Four looks, two sizes, a pill shape and the round icon-button.
import { Button } from '@iffday/ui';Looks
Button wraps Base UI’s button; className decides how it looks. .button is the default: white with an outline. primary is the one main action on a page, black with white text, reversed in dark mode. soft is grey and suits toolbars. neutral stays transparent until hovered, for Cancel and other secondary actions.
import { useT } from '@iffday/i18n';
import { Button } from '@iffday/ui';
export default function Example() {
const t = useT();
return (
<>
<Button type="button" className="button primary">
{t('common.save')}
</Button>
<Button type="button" className="button">
{t('common.edit')}
</Button>
<Button type="button" className="button soft">
{t('common.filter')}
</Button>
<Button type="button" className="button neutral">
{t('common.cancel')}
</Button>
</>
);
}Sizes and shape
The default is 38px tall and small is 31px. The site uses small more often than the default: buttons in list rows, cards and sidebars are small. pill rounds the ends fully and also takes the grey of the Pill label; the follow, edit profile and invitation buttons on profile pages use it.
import { useT } from '@iffday/i18n';
import { Button } from '@iffday/ui';
export default function Example() {
const t = useT();
return (
<>
<Button type="button" className="button primary">
{t('nav.signIn')}
</Button>
<Button type="button" className="button primary small">
{t('nav.signIn')}
</Button>
<Button type="button" className="button small pill">
{t('common.add')}
</Button>
<Button type="button" className="button primary small pill">
{t('common.add')}
</Button>
</>
);
}Icon only
.icon-button is a 34px circle in the secondary text grey; with active it turns brand red, as the Watchlist heart does. An icon-only button always needs an aria-label. A text button can carry an icon too: put it before the text, and the button leaves 7px between them.
import { useT } from '@iffday/i18n';
import { Button } from '@iffday/ui';
import { Ellipsis, Heart, Plus } from 'lucide-react';
import { useState } from 'react';
export default function Example() {
const t = useT();
const [wanted, setWanted] = useState(true);
return (
<>
<Button type="button" className="icon-button" aria-label={t('common.add')}>
<Plus size={18} />
</Button>
<Button type="button" className="icon-button" aria-label={t('common.actions')}>
<Ellipsis size={18} />
</Button>
<Button
type="button"
className={`icon-button ${wanted ? 'active' : ''}`}
aria-pressed={wanted}
aria-label={t('film.card.watchlist')}
onClick={() => setWanted(!wanted)}
>
<Heart size={18} fill={wanted ? 'currentColor' : 'none'} />
</Button>
<Button type="button" className="button small">
<Plus size={15} />
{t('common.add')}
</Button>
</>
);
}States
selected has the colors of primary and marks a pressed filter; set aria-pressed with it. A button that is saving gets disabled and aria-busy: disabled buttons drop to 45% opacity, and a busy one shows the progress cursor.
import { useT } from '@iffday/i18n';
import { Button } from '@iffday/ui';
import { useState } from 'react';
export default function Example() {
const t = useT();
const [busy, setBusy] = useState(false);
const [onlyWanted, setOnlyWanted] = useState(true);
return (
<>
<Button
type="button"
className={`button ${onlyWanted ? 'selected' : ''}`}
aria-pressed={onlyWanted}
onClick={() => setOnlyWanted(!onlyWanted)}
>
{t('film.card.watchlist')}
</Button>
<Button
type="button"
className="button primary"
disabled={busy}
aria-busy={busy}
onClick={() => {
setBusy(true);
setTimeout(() => setBusy(false), 1500);
}}
>
{busy ? t('common.loading') : t('common.save')}
</Button>
<Button type="button" className="button" disabled>
{t('common.delete')}
</Button>
</>
);
}Usage
Buttondefaults totype="submit". Outside a form, or for any button that does not submit, writetype="button".- Web code never writes a native
<button>:tests/ui-boundary.test.tsfails on one. - Navigation is a link, never a button. To make a link look like a button, give the
LinkclassName="button". - The stylesheet also has
accent(brand red) andfull(full width); no page uses them yet.
Props
| Prop | Type | Default |
|---|---|---|
type | 'submit' |
Other props go to ComponentProps<typeof BaseButton> (see the Base UI documentation).
- src/pages/cms.tsx33
- src/pages/personal.tsx24
- src/pages/cms-festivals.tsx22
- src/pages/social.tsx21
- src/pages/cms-addresses.tsx9
- src/pages/editorial.tsx9
- src/pages/film-entry.tsx9
- src/pages/film-images.tsx8
- src/pages/profile.tsx7
- src/pages/festival-directory.tsx6
- src/pages/account-settings.tsx5
- src/pages/auth.tsx5
- src/App.tsx4
- src/pages/catalog-drafts.tsx4
- src/pages/follows.tsx4
- src/pages/planner-settings.tsx4
- src/pages/programme.tsx4
- src/pages/search.tsx4
- src/review-card.tsx4
- src/film-header-debug.tsx3
- src/pages/archive-tools.tsx3
- src/pages/cms-translations.tsx3
- src/pages/wallet-extras.tsx3
- src/components.tsx2
- src/kit.tsx2
- src/pages/cms-film-reviews.tsx2
- src/pages/schedule.tsx2
- src/pages/sticker-board.tsx2
- src/film-facts.tsx1
- src/language-selector.tsx1
- src/pages/community.tsx1
- src/pages/feed.tsx1
- src/pages/festivals.tsx1